diff --git a/build.gradle b/build.gradle index 4033f74a8..a92b6eb3c 100644 --- a/build.gradle +++ b/build.gradle @@ -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}" diff --git a/gradle.properties b/gradle.properties index a0e5d84e2..17e6978dc 100644 --- a/gradle.properties +++ b/gradle.properties @@ -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 diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 4b7e1f3d3..2cf66302b 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -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 diff --git a/remappedSrc/dan200/computercraft/ComputerCraft.java b/remappedSrc/dan200/computercraft/ComputerCraft.java new file mode 100644 index 000000000..b89534663 --- /dev/null +++ b/remappedSrc/dan200/computercraft/ComputerCraft.java @@ -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 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; + } + } +} diff --git a/remappedSrc/dan200/computercraft/ComputerCraftAPIImpl.java b/remappedSrc/dan200/computercraft/ComputerCraftAPIImpl.java new file mode 100644 index 000000000..c1dd6c526 --- /dev/null +++ b/remappedSrc/dan200/computercraft/ComputerCraftAPIImpl.java @@ -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; + } +} diff --git a/remappedSrc/dan200/computercraft/api/AbstractTurtleUpgrade.java b/remappedSrc/dan200/computercraft/api/AbstractTurtleUpgrade.java new file mode 100644 index 000000000..a1a65fc0d --- /dev/null +++ b/remappedSrc/dan200/computercraft/api/AbstractTurtleUpgrade.java @@ -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; + } +} diff --git a/remappedSrc/dan200/computercraft/api/ComputerCraftAPI.java b/remappedSrc/dan200/computercraft/api/ComputerCraftAPI.java new file mode 100644 index 000000000..625d18be3 --- /dev/null +++ b/remappedSrc/dan200/computercraft/api/ComputerCraftAPI.java @@ -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 ); + } +} diff --git a/remappedSrc/dan200/computercraft/api/filesystem/IFileSystem.java b/remappedSrc/dan200/computercraft/api/filesystem/IFileSystem.java new file mode 100644 index 000000000..cc2aac128 --- /dev/null +++ b/remappedSrc/dan200/computercraft/api/filesystem/IFileSystem.java @@ -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; +} diff --git a/remappedSrc/dan200/computercraft/api/filesystem/IMount.java b/remappedSrc/dan200/computercraft/api/filesystem/IMount.java new file mode 100644 index 000000000..1fec70994 --- /dev/null +++ b/remappedSrc/dan200/computercraft/api/filesystem/IMount.java @@ -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 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 ) ); + } +} diff --git a/remappedSrc/dan200/computercraft/api/filesystem/IWritableMount.java b/remappedSrc/dan200/computercraft/api/filesystem/IWritableMount.java new file mode 100644 index 000000000..e265842e7 --- /dev/null +++ b/remappedSrc/dan200/computercraft/api/filesystem/IWritableMount.java @@ -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; +} diff --git a/remappedSrc/dan200/computercraft/api/lua/IComputerSystem.java b/remappedSrc/dan200/computercraft/api/lua/IComputerSystem.java new file mode 100644 index 000000000..1ebb4af5f --- /dev/null +++ b/remappedSrc/dan200/computercraft/api/lua/IComputerSystem.java @@ -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(); +} diff --git a/remappedSrc/dan200/computercraft/api/lua/ILuaAPI.java b/remappedSrc/dan200/computercraft/api/lua/ILuaAPI.java new file mode 100644 index 000000000..8e02b740d --- /dev/null +++ b/remappedSrc/dan200/computercraft/api/lua/ILuaAPI.java @@ -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() + { + } +} diff --git a/remappedSrc/dan200/computercraft/api/lua/ILuaAPIFactory.java b/remappedSrc/dan200/computercraft/api/lua/ILuaAPIFactory.java new file mode 100644 index 000000000..90a147832 --- /dev/null +++ b/remappedSrc/dan200/computercraft/api/lua/ILuaAPIFactory.java @@ -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 ); +} diff --git a/remappedSrc/dan200/computercraft/api/lua/ILuaContext.java b/remappedSrc/dan200/computercraft/api/lua/ILuaContext.java new file mode 100644 index 000000000..e1fc07b89 --- /dev/null +++ b/remappedSrc/dan200/computercraft/api/lua/ILuaContext.java @@ -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; +} diff --git a/remappedSrc/dan200/computercraft/api/lua/ILuaObject.java b/remappedSrc/dan200/computercraft/api/lua/ILuaObject.java new file mode 100644 index 000000000..2f09434c8 --- /dev/null +++ b/remappedSrc/dan200/computercraft/api/lua/ILuaObject.java @@ -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; +} diff --git a/remappedSrc/dan200/computercraft/api/lua/ILuaTask.java b/remappedSrc/dan200/computercraft/api/lua/ILuaTask.java new file mode 100644 index 000000000..76636f335 --- /dev/null +++ b/remappedSrc/dan200/computercraft/api/lua/ILuaTask.java @@ -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; +} diff --git a/remappedSrc/dan200/computercraft/api/lua/LuaException.java b/remappedSrc/dan200/computercraft/api/lua/LuaException.java new file mode 100644 index 000000000..2f9d2687c --- /dev/null +++ b/remappedSrc/dan200/computercraft/api/lua/LuaException.java @@ -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; + } +} diff --git a/remappedSrc/dan200/computercraft/api/media/IMedia.java b/remappedSrc/dan200/computercraft/api/media/IMedia.java new file mode 100644 index 000000000..afe574cee --- /dev/null +++ b/remappedSrc/dan200/computercraft/api/media/IMedia.java @@ -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; + } +} diff --git a/remappedSrc/dan200/computercraft/api/media/IMediaProvider.java b/remappedSrc/dan200/computercraft/api/media/IMediaProvider.java new file mode 100644 index 000000000..13784cd20 --- /dev/null +++ b/remappedSrc/dan200/computercraft/api/media/IMediaProvider.java @@ -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 ); +} diff --git a/remappedSrc/dan200/computercraft/api/network/IPacketNetwork.java b/remappedSrc/dan200/computercraft/api/network/IPacketNetwork.java new file mode 100644 index 000000000..d6b2f529c --- /dev/null +++ b/remappedSrc/dan200/computercraft/api/network/IPacketNetwork.java @@ -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 ); +} diff --git a/remappedSrc/dan200/computercraft/api/network/IPacketReceiver.java b/remappedSrc/dan200/computercraft/api/network/IPacketReceiver.java new file mode 100644 index 000000000..8be74f30e --- /dev/null +++ b/remappedSrc/dan200/computercraft/api/network/IPacketReceiver.java @@ -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 ); +} diff --git a/remappedSrc/dan200/computercraft/api/network/IPacketSender.java b/remappedSrc/dan200/computercraft/api/network/IPacketSender.java new file mode 100644 index 000000000..c28f06dee --- /dev/null +++ b/remappedSrc/dan200/computercraft/api/network/IPacketSender.java @@ -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(); +} diff --git a/remappedSrc/dan200/computercraft/api/network/Packet.java b/remappedSrc/dan200/computercraft/api/network/Packet.java new file mode 100644 index 000000000..df7408682 --- /dev/null +++ b/remappedSrc/dan200/computercraft/api/network/Packet.java @@ -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; + } +} diff --git a/remappedSrc/dan200/computercraft/api/network/wired/IWiredElement.java b/remappedSrc/dan200/computercraft/api/network/wired/IWiredElement.java new file mode 100644 index 000000000..a7a76d7b0 --- /dev/null +++ b/remappedSrc/dan200/computercraft/api/network/wired/IWiredElement.java @@ -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 ) + { + } +} diff --git a/remappedSrc/dan200/computercraft/api/network/wired/IWiredNetwork.java b/remappedSrc/dan200/computercraft/api/network/wired/IWiredNetwork.java new file mode 100644 index 000000000..bd1431a44 --- /dev/null +++ b/remappedSrc/dan200/computercraft/api/network/wired/IWiredNetwork.java @@ -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 peripherals ); +} diff --git a/remappedSrc/dan200/computercraft/api/network/wired/IWiredNetworkChange.java b/remappedSrc/dan200/computercraft/api/network/wired/IWiredNetworkChange.java new file mode 100644 index 000000000..9769bbf9b --- /dev/null +++ b/remappedSrc/dan200/computercraft/api/network/wired/IWiredNetworkChange.java @@ -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 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 peripheralsAdded(); +} diff --git a/remappedSrc/dan200/computercraft/api/network/wired/IWiredNode.java b/remappedSrc/dan200/computercraft/api/network/wired/IWiredNode.java new file mode 100644 index 000000000..a67d06bba --- /dev/null +++ b/remappedSrc/dan200/computercraft/api/network/wired/IWiredNode.java @@ -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 peripherals ) + { + getNetwork().updatePeripherals( this, peripherals ); + } +} diff --git a/remappedSrc/dan200/computercraft/api/network/wired/IWiredSender.java b/remappedSrc/dan200/computercraft/api/network/wired/IWiredSender.java new file mode 100644 index 000000000..ccc182b93 --- /dev/null +++ b/remappedSrc/dan200/computercraft/api/network/wired/IWiredSender.java @@ -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(); +} diff --git a/remappedSrc/dan200/computercraft/api/peripheral/IComputerAccess.java b/remappedSrc/dan200/computercraft/api/peripheral/IComputerAccess.java new file mode 100644 index 000000000..26f8cf154 --- /dev/null +++ b/remappedSrc/dan200/computercraft/api/peripheral/IComputerAccess.java @@ -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 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 not 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; + } +} diff --git a/remappedSrc/dan200/computercraft/api/peripheral/IPeripheral.java b/remappedSrc/dan200/computercraft/api/peripheral/IPeripheral.java new file mode 100644 index 000000000..965d635c7 --- /dev/null +++ b/remappedSrc/dan200/computercraft/api/peripheral/IPeripheral.java @@ -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()}.
+ * Lua values of type "string" will be represented by Object type String.
+ * Lua values of type "number" will be represented by Object type Double.
+ * Lua values of type "boolean" will be represented by Object type Boolean.
+ * Lua values of type "table" will be represented by Object type Map.
+ * Lua values of any other type will be represented by a null object.
+ * 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 ); +} diff --git a/remappedSrc/dan200/computercraft/api/peripheral/IPeripheralProvider.java b/remappedSrc/dan200/computercraft/api/peripheral/IPeripheralProvider.java new file mode 100644 index 000000000..bd97e1ed3 --- /dev/null +++ b/remappedSrc/dan200/computercraft/api/peripheral/IPeripheralProvider.java @@ -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 ); +} diff --git a/remappedSrc/dan200/computercraft/api/peripheral/IPeripheralTile.java b/remappedSrc/dan200/computercraft/api/peripheral/IPeripheralTile.java new file mode 100644 index 000000000..4675139c6 --- /dev/null +++ b/remappedSrc/dan200/computercraft/api/peripheral/IPeripheralTile.java @@ -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 ); +} diff --git a/remappedSrc/dan200/computercraft/api/peripheral/IWorkMonitor.java b/remappedSrc/dan200/computercraft/api/peripheral/IWorkMonitor.java new file mode 100644 index 000000000..0c0783026 --- /dev/null +++ b/remappedSrc/dan200/computercraft/api/peripheral/IWorkMonitor.java @@ -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; + } +} diff --git a/remappedSrc/dan200/computercraft/api/pocket/AbstractPocketUpgrade.java b/remappedSrc/dan200/computercraft/api/pocket/AbstractPocketUpgrade.java new file mode 100644 index 000000000..eb20ec9e1 --- /dev/null +++ b/remappedSrc/dan200/computercraft/api/pocket/AbstractPocketUpgrade.java @@ -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; + } +} diff --git a/remappedSrc/dan200/computercraft/api/pocket/IPocketAccess.java b/remappedSrc/dan200/computercraft/api/pocket/IPocketAccess.java new file mode 100644 index 000000000..e2e78dedf --- /dev/null +++ b/remappedSrc/dan200/computercraft/api/pocket/IPocketAccess.java @@ -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 getUpgrades(); +} diff --git a/remappedSrc/dan200/computercraft/api/pocket/IPocketUpgrade.java b/remappedSrc/dan200/computercraft/api/pocket/IPocketUpgrade.java new file mode 100644 index 000000000..495033db1 --- /dev/null +++ b/remappedSrc/dan200/computercraft/api/pocket/IPocketUpgrade.java @@ -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; + } +} diff --git a/remappedSrc/dan200/computercraft/api/redstone/IBundledRedstoneProvider.java b/remappedSrc/dan200/computercraft/api/redstone/IBundledRedstoneProvider.java new file mode 100644 index 000000000..1602a546c --- /dev/null +++ b/remappedSrc/dan200/computercraft/api/redstone/IBundledRedstoneProvider.java @@ -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 ); +} diff --git a/remappedSrc/dan200/computercraft/api/turtle/ITurtleAccess.java b/remappedSrc/dan200/computercraft/api/turtle/ITurtleAccess.java new file mode 100644 index 000000000..e3ac2922b --- /dev/null +++ b/remappedSrc/dan200/computercraft/api/turtle/ITurtleAccess.java @@ -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 ); +} diff --git a/remappedSrc/dan200/computercraft/api/turtle/ITurtleCommand.java b/remappedSrc/dan200/computercraft/api/turtle/ITurtleCommand.java new file mode 100644 index 000000000..20dfd0808 --- /dev/null +++ b/remappedSrc/dan200/computercraft/api/turtle/ITurtleCommand.java @@ -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 ); +} diff --git a/remappedSrc/dan200/computercraft/api/turtle/ITurtleUpgrade.java b/remappedSrc/dan200/computercraft/api/turtle/ITurtleUpgrade.java new file mode 100644 index 000000000..2c020902f --- /dev/null +++ b/remappedSrc/dan200/computercraft/api/turtle/ITurtleUpgrade.java @@ -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 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 ) + { + } +} diff --git a/remappedSrc/dan200/computercraft/api/turtle/TurtleAnimation.java b/remappedSrc/dan200/computercraft/api/turtle/TurtleAnimation.java new file mode 100644 index 000000000..58d36719f --- /dev/null +++ b/remappedSrc/dan200/computercraft/api/turtle/TurtleAnimation.java @@ -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 behind it, and + * moves into this one. + */ + MoveForward, + + /** + * Make the turtle move backwards. Note that the animation starts from the block in front it, and + * moves into this one. + */ + MoveBack, + + /** + * Make the turtle move backwards. Note that the animation starts from the block above it, and + * moves into this one. + */ + MoveUp, + + /** + * Make the turtle move backwards. Note that the animation starts from the block below it, and + * moves into this one. + */ + MoveDown, + + /** + * Turn the turtle to the left. Note that the animation starts with the turtle facing right, 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 right, 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, +} diff --git a/remappedSrc/dan200/computercraft/api/turtle/TurtleCommandResult.java b/remappedSrc/dan200/computercraft/api/turtle/TurtleCommandResult.java new file mode 100644 index 000000000..2debebaba --- /dev/null +++ b/remappedSrc/dan200/computercraft/api/turtle/TurtleCommandResult.java @@ -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; + } +} diff --git a/remappedSrc/dan200/computercraft/api/turtle/TurtleSide.java b/remappedSrc/dan200/computercraft/api/turtle/TurtleSide.java new file mode 100644 index 000000000..9bfabddf9 --- /dev/null +++ b/remappedSrc/dan200/computercraft/api/turtle/TurtleSide.java @@ -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, +} diff --git a/remappedSrc/dan200/computercraft/api/turtle/TurtleUpgradeType.java b/remappedSrc/dan200/computercraft/api/turtle/TurtleUpgradeType.java new file mode 100644 index 000000000..447adf289 --- /dev/null +++ b/remappedSrc/dan200/computercraft/api/turtle/TurtleUpgradeType.java @@ -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; + } +} diff --git a/remappedSrc/dan200/computercraft/api/turtle/TurtleVerb.java b/remappedSrc/dan200/computercraft/api/turtle/TurtleVerb.java new file mode 100644 index 000000000..af69e60a3 --- /dev/null +++ b/remappedSrc/dan200/computercraft/api/turtle/TurtleVerb.java @@ -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, +} diff --git a/remappedSrc/dan200/computercraft/api/turtle/event/FakePlayer.java b/remappedSrc/dan200/computercraft/api/turtle/event/FakePlayer.java new file mode 100644 index 000000000..1f3408854 --- /dev/null +++ b/remappedSrc/dan200/computercraft/api/turtle/event/FakePlayer.java @@ -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 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> recipes ) + { + return 0; + } + + @Override + public int unlockRecipes( Collection> 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> 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> listener ) + { + } + } +} diff --git a/remappedSrc/dan200/computercraft/api/turtle/event/TurtleAction.java b/remappedSrc/dan200/computercraft/api/turtle/event/TurtleAction.java new file mode 100644 index 000000000..131e36613 --- /dev/null +++ b/remappedSrc/dan200/computercraft/api/turtle/event/TurtleAction.java @@ -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, +} diff --git a/remappedSrc/dan200/computercraft/api/turtle/event/TurtleActionEvent.java b/remappedSrc/dan200/computercraft/api/turtle/event/TurtleActionEvent.java new file mode 100644 index 000000000..a87272be3 --- /dev/null +++ b/remappedSrc/dan200/computercraft/api/turtle/event/TurtleActionEvent.java @@ -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; + } +} diff --git a/remappedSrc/dan200/computercraft/api/turtle/event/TurtleAttackEvent.java b/remappedSrc/dan200/computercraft/api/turtle/event/TurtleAttackEvent.java new file mode 100644 index 000000000..32fdca3b4 --- /dev/null +++ b/remappedSrc/dan200/computercraft/api/turtle/event/TurtleAttackEvent.java @@ -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; + } +} diff --git a/remappedSrc/dan200/computercraft/api/turtle/event/TurtleBlockEvent.java b/remappedSrc/dan200/computercraft/api/turtle/event/TurtleBlockEvent.java new file mode 100644 index 000000000..963ec42f1 --- /dev/null +++ b/remappedSrc/dan200/computercraft/api/turtle/event/TurtleBlockEvent.java @@ -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 data; + + public Inspect( @Nonnull ITurtleAccess turtle, @Nonnull FakePlayer player, @Nonnull World world, @Nonnull BlockPos pos, @Nonnull BlockState state, @Nonnull Map 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 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 newData ) + { + Objects.requireNonNull( newData, "newData cannot be null" ); + data.putAll( newData ); + } + } +} diff --git a/remappedSrc/dan200/computercraft/api/turtle/event/TurtleEvent.java b/remappedSrc/dan200/computercraft/api/turtle/event/TurtleEvent.java new file mode 100644 index 000000000..142a3a45a --- /dev/null +++ b/remappedSrc/dan200/computercraft/api/turtle/event/TurtleEvent.java @@ -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(); + } +} diff --git a/remappedSrc/dan200/computercraft/api/turtle/event/TurtleInspectItemEvent.java b/remappedSrc/dan200/computercraft/api/turtle/event/TurtleInspectItemEvent.java new file mode 100644 index 000000000..1bd2164d2 --- /dev/null +++ b/remappedSrc/dan200/computercraft/api/turtle/event/TurtleInspectItemEvent.java @@ -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 data; + + public TurtleInspectItemEvent( @Nonnull ITurtleAccess turtle, @Nonnull ItemStack stack, @Nonnull Map 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 not 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 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 newData ) + { + Objects.requireNonNull( newData, "newData cannot be null" ); + data.putAll( newData ); + } +} diff --git a/remappedSrc/dan200/computercraft/api/turtle/event/TurtleInventoryEvent.java b/remappedSrc/dan200/computercraft/api/turtle/event/TurtleInventoryEvent.java new file mode 100644 index 000000000..a7a99be25 --- /dev/null +++ b/remappedSrc/dan200/computercraft/api/turtle/event/TurtleInventoryEvent.java @@ -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 not be modified. + */ + @Nonnull + public ItemStack getStack() + { + return stack; + } + } +} diff --git a/remappedSrc/dan200/computercraft/api/turtle/event/TurtlePlayerEvent.java b/remappedSrc/dan200/computercraft/api/turtle/event/TurtlePlayerEvent.java new file mode 100644 index 000000000..3d7534dcd --- /dev/null +++ b/remappedSrc/dan200/computercraft/api/turtle/event/TurtlePlayerEvent.java @@ -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; + } +} diff --git a/remappedSrc/dan200/computercraft/api/turtle/event/TurtleRefuelEvent.java b/remappedSrc/dan200/computercraft/api/turtle/event/TurtleRefuelEvent.java new file mode 100644 index 000000000..33dbc3d2f --- /dev/null +++ b/remappedSrc/dan200/computercraft/api/turtle/event/TurtleRefuelEvent.java @@ -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 ); + } +} diff --git a/remappedSrc/dan200/computercraft/client/ClientRegistry.java b/remappedSrc/dan200/computercraft/client/ClientRegistry.java new file mode 100644 index 000000000..c151fcb3d --- /dev/null +++ b/remappedSrc/dan200/computercraft/client/ClientRegistry.java @@ -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 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 ); + } +} diff --git a/remappedSrc/dan200/computercraft/client/ClientTableFormatter.java b/remappedSrc/dan200/computercraft/client/ClientTableFormatter.java new file mode 100644 index 000000000..1b0c1d55a --- /dev/null +++ b/remappedSrc/dan200/computercraft/client/ClientTableFormatter.java @@ -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 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; + } +} diff --git a/remappedSrc/dan200/computercraft/client/FrameInfo.java b/remappedSrc/dan200/computercraft/client/FrameInfo.java new file mode 100644 index 000000000..a5b5d5b4e --- /dev/null +++ b/remappedSrc/dan200/computercraft/client/FrameInfo.java @@ -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++; + } +} diff --git a/remappedSrc/dan200/computercraft/client/ModMenuIntegration.java b/remappedSrc/dan200/computercraft/client/ModMenuIntegration.java new file mode 100644 index 000000000..207e217f9 --- /dev/null +++ b/remappedSrc/dan200/computercraft/client/ModMenuIntegration.java @@ -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 getConfigScreenFactory() + { + return GuiConfig::getScreen; + } + + @Override + public String getModId() + { + return ComputerCraft.MOD_ID; + } +} diff --git a/remappedSrc/dan200/computercraft/client/gui/FixedWidthFontRenderer.java b/remappedSrc/dan200/computercraft/client/gui/FixedWidthFontRenderer.java new file mode 100644 index 000000000..84358d749 --- /dev/null +++ b/remappedSrc/dan200/computercraft/client/gui/FixedWidthFontRenderer.java @@ -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 ); + } +} diff --git a/remappedSrc/dan200/computercraft/client/gui/GuiComputer.java b/remappedSrc/dan200/computercraft/client/gui/GuiComputer.java new file mode 100644 index 000000000..5cea47ff0 --- /dev/null +++ b/remappedSrc/dan200/computercraft/client/gui/GuiComputer.java @@ -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 extends HandledScreen +{ + 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 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 ); + } +} diff --git a/remappedSrc/dan200/computercraft/client/gui/GuiConfig.java b/remappedSrc/dan200/computercraft/client/gui/GuiConfig.java new file mode 100644 index 000000000..53db385df --- /dev/null +++ b/remappedSrc/dan200/computercraft/client/gui/GuiConfig.java @@ -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; + } +} diff --git a/remappedSrc/dan200/computercraft/client/gui/GuiDiskDrive.java b/remappedSrc/dan200/computercraft/client/gui/GuiDiskDrive.java new file mode 100644 index 000000000..9ba78ead9 --- /dev/null +++ b/remappedSrc/dan200/computercraft/client/gui/GuiDiskDrive.java @@ -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 +{ + 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 ); + } +} diff --git a/remappedSrc/dan200/computercraft/client/gui/GuiPocketComputer.java b/remappedSrc/dan200/computercraft/client/gui/GuiPocketComputer.java new file mode 100644 index 000000000..8220a80b3 --- /dev/null +++ b/remappedSrc/dan200/computercraft/client/gui/GuiPocketComputer.java @@ -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 +{ + 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; + } +} diff --git a/remappedSrc/dan200/computercraft/client/gui/GuiPrinter.java b/remappedSrc/dan200/computercraft/client/gui/GuiPrinter.java new file mode 100644 index 000000000..ea9390d94 --- /dev/null +++ b/remappedSrc/dan200/computercraft/client/gui/GuiPrinter.java @@ -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 +{ + 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 ); + } +} diff --git a/remappedSrc/dan200/computercraft/client/gui/GuiPrintout.java b/remappedSrc/dan200/computercraft/client/gui/GuiPrintout.java new file mode 100644 index 000000000..eb6add708 --- /dev/null +++ b/remappedSrc/dan200/computercraft/client/gui/GuiPrintout.java @@ -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 +{ + 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 ); + } +} diff --git a/remappedSrc/dan200/computercraft/client/gui/GuiTurtle.java b/remappedSrc/dan200/computercraft/client/gui/GuiTurtle.java new file mode 100644 index 000000000..6359d145f --- /dev/null +++ b/remappedSrc/dan200/computercraft/client/gui/GuiTurtle.java @@ -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 +{ + 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 ); + } +} diff --git a/remappedSrc/dan200/computercraft/client/gui/widgets/WidgetTerminal.java b/remappedSrc/dan200/computercraft/client/gui/widgets/WidgetTerminal.java new file mode 100644 index 000000000..bb82b74aa --- /dev/null +++ b/remappedSrc/dan200/computercraft/client/gui/widgets/WidgetTerminal.java @@ -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 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 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 ); + } +} diff --git a/remappedSrc/dan200/computercraft/client/gui/widgets/WidgetWrapper.java b/remappedSrc/dan200/computercraft/client/gui/widgets/WidgetWrapper.java new file mode 100644 index 000000000..b1a209f85 --- /dev/null +++ b/remappedSrc/dan200/computercraft/client/gui/widgets/WidgetWrapper.java @@ -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; + } +} diff --git a/remappedSrc/dan200/computercraft/client/proxy/ComputerCraftProxyClient.java b/remappedSrc/dan200/computercraft/client/proxy/ComputerCraftProxyClient.java new file mode 100644 index 000000000..3b4cfb046 --- /dev/null +++ b/remappedSrc/dan200/computercraft/client/proxy/ComputerCraftProxyClient.java @@ -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(); + } + } + } + */ +} diff --git a/remappedSrc/dan200/computercraft/client/render/CableHighlightRenderer.java b/remappedSrc/dan200/computercraft/client/render/CableHighlightRenderer.java new file mode 100644 index 000000000..973fe2889 --- /dev/null +++ b/remappedSrc/dan200/computercraft/client/render/CableHighlightRenderer.java @@ -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; + } +} diff --git a/remappedSrc/dan200/computercraft/client/render/ItemMapLikeRenderer.java b/remappedSrc/dan200/computercraft/client/render/ItemMapLikeRenderer.java new file mode 100644 index 000000000..a94aad8a9 --- /dev/null +++ b/remappedSrc/dan200/computercraft/client/render/ItemMapLikeRenderer.java @@ -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 ); + } +} diff --git a/remappedSrc/dan200/computercraft/client/render/ItemPocketRenderer.java b/remappedSrc/dan200/computercraft/client/render/ItemPocketRenderer.java new file mode 100644 index 000000000..ad5d83cc2 --- /dev/null +++ b/remappedSrc/dan200/computercraft/client/render/ItemPocketRenderer.java @@ -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(); + } +} diff --git a/remappedSrc/dan200/computercraft/client/render/ItemPrintoutRenderer.java b/remappedSrc/dan200/computercraft/client/render/ItemPrintoutRenderer.java new file mode 100644 index 000000000..ed0aa96ca --- /dev/null +++ b/remappedSrc/dan200/computercraft/client/render/ItemPrintoutRenderer.java @@ -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 ) ); + } +} diff --git a/remappedSrc/dan200/computercraft/client/render/ModelTransformer.java b/remappedSrc/dan200/computercraft/client/render/ModelTransformer.java new file mode 100644 index 000000000..dd4987914 --- /dev/null +++ b/remappedSrc/dan200/computercraft/client/render/ModelTransformer.java @@ -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 output, List input, Matrix4f transform ) + { + transformQuadsTo( VertexFormats.POSITION_COLOR_TEXTURE_LIGHT_NORMAL, output, input, transform ); + } + + public static void transformQuadsTo( VertexFormat format, List output, List 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; + } +} diff --git a/remappedSrc/dan200/computercraft/client/render/MonitorHighlightRenderer.java b/remappedSrc/dan200/computercraft/client/render/MonitorHighlightRenderer.java new file mode 100644 index 000000000..8d98ec476 --- /dev/null +++ b/remappedSrc/dan200/computercraft/client/render/MonitorHighlightRenderer.java @@ -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 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(); + } +} diff --git a/remappedSrc/dan200/computercraft/client/render/PrintoutRenderer.java b/remappedSrc/dan200/computercraft/client/render/PrintoutRenderer.java new file mode 100644 index 000000000..ecce1ae4f --- /dev/null +++ b/remappedSrc/dan200/computercraft/client/render/PrintoutRenderer.java @@ -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 )); + } +} diff --git a/remappedSrc/dan200/computercraft/client/render/TileEntityCableRenderer.java b/remappedSrc/dan200/computercraft/client/render/TileEntityCableRenderer.java new file mode 100644 index 000000000..b18696683 --- /dev/null +++ b/remappedSrc/dan200/computercraft/client/render/TileEntityCableRenderer.java @@ -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 +{ + 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(); + } +} diff --git a/remappedSrc/dan200/computercraft/client/render/TileEntityMonitorRenderer.java b/remappedSrc/dan200/computercraft/client/render/TileEntityMonitorRenderer.java new file mode 100644 index 000000000..11dd78163 --- /dev/null +++ b/remappedSrc/dan200/computercraft/client/render/TileEntityMonitorRenderer.java @@ -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 +{ + @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(); + } + } +} diff --git a/remappedSrc/dan200/computercraft/client/render/TileEntityTurtleRenderer.java b/remappedSrc/dan200/computercraft/client/render/TileEntityTurtleRenderer.java new file mode 100644 index 000000000..0fe21c50c --- /dev/null +++ b/remappedSrc/dan200/computercraft/client/render/TileEntityTurtleRenderer.java @@ -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 +{ + 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 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 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(); + } +} diff --git a/remappedSrc/dan200/computercraft/client/render/TurtleModelLoader.java b/remappedSrc/dan200/computercraft/client/render/TurtleModelLoader.java new file mode 100644 index 000000000..a678fd96d --- /dev/null +++ b/remappedSrc/dan200/computercraft/client/render/TurtleModelLoader.java @@ -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 getModelDependencies() + { + return Arrays.asList( family, COLOUR_TURTLE_MODEL ); + } + + @Nonnull + @Override + public Collection getTextureDependencies( @Nonnull Function modelGetter, @Nonnull Set 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 spriteGetter, @Nonnull ModelBakeSettings state ) + { + return new TurtleSmartItemModel( + loader, + loader.getOrLoadModel( family ).bake( loader, spriteGetter, state ), + loader.getOrLoadModel( COLOUR_TURTLE_MODEL ).bake( loader, spriteGetter, state ) + ); + } + } +} diff --git a/remappedSrc/dan200/computercraft/client/render/TurtleMultiModel.java b/remappedSrc/dan200/computercraft/client/render/TurtleMultiModel.java new file mode 100644 index 000000000..1a23bfc07 --- /dev/null +++ b/remappedSrc/dan200/computercraft/client/render/TurtleMultiModel.java @@ -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 m_generalQuads = null; + private Map> 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 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 buildQuads( BlockState state, Direction side, Random rand ) + { + ArrayList 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; + } +} diff --git a/remappedSrc/dan200/computercraft/client/render/TurtleSmartItemModel.java b/remappedSrc/dan200/computercraft/client/render/TurtleSmartItemModel.java new file mode 100644 index 000000000..4fbf7e04f --- /dev/null +++ b/remappedSrc/dan200/computercraft/client/render/TurtleSmartItemModel.java @@ -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 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 leftModel = combo.m_leftUpgrade != null ? combo.m_leftUpgrade.getModel( null, TurtleSide.Left ) : null; + Pair 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 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(); + } +} diff --git a/remappedSrc/dan200/computercraft/core/apis/AddressPredicate.java b/remappedSrc/dan200/computercraft/core/apis/AddressPredicate.java new file mode 100644 index 000000000..430017dad --- /dev/null +++ b/remappedSrc/dan200/computercraft/core/apis/AddressPredicate.java @@ -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 wildcards; + private final List ranges; + + public AddressPredicate( String... filters ) + { + this( Arrays.asList( filters ) ); + } + + public AddressPredicate( Iterable filters ) + { + List wildcards = this.wildcards = new ArrayList<>(); + List 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 ) ); + + } +} diff --git a/remappedSrc/dan200/computercraft/core/apis/ApiFactories.java b/remappedSrc/dan200/computercraft/core/apis/ApiFactories.java new file mode 100644 index 000000000..e37b7b594 --- /dev/null +++ b/remappedSrc/dan200/computercraft/core/apis/ApiFactories.java @@ -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 factories = new LinkedHashSet<>(); + private static final Collection 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 getAll() + { + return factoriesView; + } +} diff --git a/remappedSrc/dan200/computercraft/core/apis/ArgumentHelper.java b/remappedSrc/dan200/computercraft/core/apis/ArgumentHelper.java new file mode 100644 index 000000000..1e04552ff --- /dev/null +++ b/remappedSrc/dan200/computercraft/core/apis/ArgumentHelper.java @@ -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 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) 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 optTable( @Nonnull Object[] args, int index, Map def ) throws LuaException + { + Object value = index < args.length ? args[index] : null; + if( value == null ) + { + return def; + } + else if( value instanceof Map ) + { + return (Map) 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; + } + } +} diff --git a/remappedSrc/dan200/computercraft/core/apis/ComputerAccess.java b/remappedSrc/dan200/computercraft/core/apis/ComputerAccess.java new file mode 100644 index 000000000..52341b92b --- /dev/null +++ b/remappedSrc/dan200/computercraft/core/apis/ComputerAccess.java @@ -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 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; + } + } +} diff --git a/remappedSrc/dan200/computercraft/core/apis/FSAPI.java b/remappedSrc/dan200/computercraft/core/apis/FSAPI.java new file mode 100644 index 000000000..2f1f92299 --- /dev/null +++ b/remappedSrc/dan200/computercraft/core/apis/FSAPI.java @@ -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 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 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 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 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 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 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 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 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; + } + } +} diff --git a/remappedSrc/dan200/computercraft/core/apis/HTTPAPI.java b/remappedSrc/dan200/computercraft/core/apis/HTTPAPI.java new file mode 100644 index 000000000..23fb93ed3 --- /dev/null +++ b/remappedSrc/dan200/computercraft/core/apis/HTTPAPI.java @@ -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 checkUrls = new ResourceGroup<>(); + private final ResourceGroup requests = new ResourceQueue<>( () -> ComputerCraft.httpMaxRequests ); + private final ResourceGroup 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 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 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; + } +} diff --git a/remappedSrc/dan200/computercraft/core/apis/IAPIEnvironment.java b/remappedSrc/dan200/computercraft/core/apis/IAPIEnvironment.java new file mode 100644 index 000000000..92ef98d32 --- /dev/null +++ b/remappedSrc/dan200/computercraft/core/apis/IAPIEnvironment.java @@ -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 ); + } +} diff --git a/remappedSrc/dan200/computercraft/core/apis/ILuaAPI.java b/remappedSrc/dan200/computercraft/core/apis/ILuaAPI.java new file mode 100644 index 000000000..63d49879c --- /dev/null +++ b/remappedSrc/dan200/computercraft/core/apis/ILuaAPI.java @@ -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 ); + } +} diff --git a/remappedSrc/dan200/computercraft/core/apis/LuaDateTime.java b/remappedSrc/dan200/computercraft/core/apis/LuaDateTime.java new file mode 100644 index 000000000..516f89f36 --- /dev/null +++ b/remappedSrc/dan200/computercraft/core/apis/LuaDateTime.java @@ -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 toTable( TemporalAccessor date, ZoneId offset, Instant instant ) + { + HashMap 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 adjustInto( R temporal, long newValue ) + { + return (R) temporal.with( field, newValue ); + } + }; + } +} diff --git a/remappedSrc/dan200/computercraft/core/apis/OSAPI.java b/remappedSrc/dan200/computercraft/core/apis/OSAPI.java new file mode 100644 index 000000000..fbbfe5e0f --- /dev/null +++ b/remappedSrc/dan200/computercraft/core/apis/OSAPI.java @@ -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 m_timers; + private final Map 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 + { + 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> it = m_timers.entrySet().iterator(); + while( it.hasNext() ) + { + Map.Entry 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> it = m_alarms.entrySet().iterator(); + while( it.hasNext() ) + { + Map.Entry 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(); + } +} diff --git a/remappedSrc/dan200/computercraft/core/apis/PeripheralAPI.java b/remappedSrc/dan200/computercraft/core/apis/PeripheralAPI.java new file mode 100644 index 000000000..eb77fe12b --- /dev/null +++ b/remappedSrc/dan200/computercraft/core/apis/PeripheralAPI.java @@ -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 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 getAvailablePeripherals() + { + if( !m_attached ) + { + throw new RuntimeException( "You are not attached to this Computer" ); + } + + Map 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 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; + } + } +} diff --git a/remappedSrc/dan200/computercraft/core/apis/RedstoneAPI.java b/remappedSrc/dan200/computercraft/core/apis/RedstoneAPI.java new file mode 100644 index 000000000..d36ba9186 --- /dev/null +++ b/remappedSrc/dan200/computercraft/core/apis/RedstoneAPI.java @@ -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 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; + } +} diff --git a/remappedSrc/dan200/computercraft/core/apis/TableHelper.java b/remappedSrc/dan200/computercraft/core/apis/TableHelper.java new file mode 100644 index 000000000..6a59d2f3c --- /dev/null +++ b/remappedSrc/dan200/computercraft/core/apis/TableHelper.java @@ -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 getTableField( @Nonnull Map table, @Nonnull String key ) throws LuaException + { + Object value = table.get( key ); + if( value instanceof Map ) + { + return (Map) 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 optTableField( @Nonnull Map table, @Nonnull String key, Map def ) throws LuaException + { + Object value = table.get( key ); + if( value == null ) + { + return def; + } + else if( value instanceof Map ) + { + return (Map) 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; + } + } +} diff --git a/remappedSrc/dan200/computercraft/core/apis/TermAPI.java b/remappedSrc/dan200/computercraft/core/apis/TermAPI.java new file mode 100644 index 000000000..70460d3ed --- /dev/null +++ b/remappedSrc/dan200/computercraft/core/apis/TermAPI.java @@ -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; + } +} diff --git a/remappedSrc/dan200/computercraft/core/apis/handles/ArrayByteChannel.java b/remappedSrc/dan200/computercraft/core/apis/handles/ArrayByteChannel.java new file mode 100644 index 000000000..9e7b9a6bf --- /dev/null +++ b/remappedSrc/dan200/computercraft/core/apis/handles/ArrayByteChannel.java @@ -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; + } +} diff --git a/remappedSrc/dan200/computercraft/core/apis/handles/BinaryReadableHandle.java b/remappedSrc/dan200/computercraft/core/apis/handles/BinaryReadableHandle.java new file mode 100644 index 000000000..9a1911cb3 --- /dev/null +++ b/remappedSrc/dan200/computercraft/core/apis/handles/BinaryReadableHandle.java @@ -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 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; + } + } +} diff --git a/remappedSrc/dan200/computercraft/core/apis/handles/BinaryWritableHandle.java b/remappedSrc/dan200/computercraft/core/apis/handles/BinaryWritableHandle.java new file mode 100644 index 000000000..2f38742a8 --- /dev/null +++ b/remappedSrc/dan200/computercraft/core/apis/handles/BinaryWritableHandle.java @@ -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; + } + } +} diff --git a/remappedSrc/dan200/computercraft/core/apis/handles/EncodedReadableHandle.java b/remappedSrc/dan200/computercraft/core/apis/handles/EncodedReadableHandle.java new file mode 100644 index 000000000..412d29f09 --- /dev/null +++ b/remappedSrc/dan200/computercraft/core/apis/handles/EncodedReadableHandle.java @@ -0,0 +1,176 @@ +/* + * 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 dan200.computercraft.api.lua.ILuaContext; +import dan200.computercraft.api.lua.LuaException; + +import javax.annotation.Nonnull; +import java.io.BufferedReader; +import java.io.Closeable; +import java.io.IOException; +import java.nio.channels.Channels; +import java.nio.channels.ReadableByteChannel; +import java.nio.charset.Charset; +import java.nio.charset.CharsetDecoder; +import java.nio.charset.CodingErrorAction; +import java.nio.charset.StandardCharsets; + +import static dan200.computercraft.core.apis.ArgumentHelper.optBoolean; +import static dan200.computercraft.core.apis.ArgumentHelper.optInt; + +public class EncodedReadableHandle extends HandleGeneric +{ + private static final int BUFFER_SIZE = 8192; + + private BufferedReader m_reader; + + public EncodedReadableHandle( @Nonnull BufferedReader reader, @Nonnull Closeable closable ) + { + super( closable ); + m_reader = reader; + } + + public EncodedReadableHandle( @Nonnull BufferedReader reader ) + { + this( reader, reader ); + } + + @Nonnull + @Override + public String[] getMethodNames() + { + return new String[] { + "readLine", + "readAll", + "read", + "close", + }; + } + + @Override + public Object[] callMethod( @Nonnull ILuaContext context, int method, @Nonnull Object[] args ) throws LuaException + { + switch( method ) + { + case 0: // readLine + { + checkOpen(); + boolean withTrailing = optBoolean( args, 0, false ); + try + { + String line = m_reader.readLine(); + if( line != null ) + { + // While this is technically inaccurate, it's better than nothing + if( withTrailing ) line += "\n"; + return new Object[] { line }; + } + else + { + return null; + } + } + catch( IOException e ) + { + return null; + } + } + case 1: // readAll + checkOpen(); + try + { + StringBuilder result = new StringBuilder(); + String line = m_reader.readLine(); + while( line != null ) + { + result.append( line ); + line = m_reader.readLine(); + if( line != null ) + { + result.append( "\n" ); + } + } + return new Object[] { result.toString() }; + } + catch( IOException e ) + { + return null; + } + case 2: // read + checkOpen(); + try + { + int count = optInt( args, 0, 1 ); + if( count < 0 ) + { + // Whilst this may seem absurd to allow reading 0 characters, PUC Lua it so + // it seems best to remain somewhat consistent. + throw new LuaException( "Cannot read a negative number of characters" ); + } + else if( count <= BUFFER_SIZE ) + { + // If we've got a small count, then allocate that and read it. + char[] chars = new char[count]; + int read = m_reader.read( chars ); + + return read < 0 ? null : new Object[] { new String( chars, 0, read ) }; + } + else + { + // If we've got a large count, read in bunches of 8192. + char[] buffer = new char[BUFFER_SIZE]; + + // Read the initial set of characters, failing if none are read. + int read = m_reader.read( buffer, 0, Math.min( buffer.length, count ) ); + if( read < 0 ) return null; + + StringBuilder out = new StringBuilder( read ); + int totalRead = read; + out.append( buffer, 0, read ); + + // Otherwise read until we either reach the limit or we no longer consume + // the full buffer. + while( read >= BUFFER_SIZE && totalRead < count ) + { + read = m_reader.read( buffer, 0, Math.min( BUFFER_SIZE, count - totalRead ) ); + if( read < 0 ) break; + + totalRead += read; + out.append( buffer, 0, read ); + } + + return new Object[] { out.toString() }; + } + } + catch( IOException e ) + { + return null; + } + case 3: // close + close(); + return null; + default: + return null; + } + } + + public static BufferedReader openUtf8( ReadableByteChannel channel ) + { + return open( channel, StandardCharsets.UTF_8 ); + } + + public static BufferedReader open( ReadableByteChannel channel, Charset charset ) + { + // Create a charset decoder with the same properties as StreamDecoder does for + // InputStreams: namely, replace everything instead of erroring. + CharsetDecoder decoder = charset.newDecoder() + .onMalformedInput( CodingErrorAction.REPLACE ) + .onUnmappableCharacter( CodingErrorAction.REPLACE ); + return new BufferedReader( Channels.newReader( channel, decoder, -1 ) ); + } +} diff --git a/remappedSrc/dan200/computercraft/core/apis/handles/EncodedWritableHandle.java b/remappedSrc/dan200/computercraft/core/apis/handles/EncodedWritableHandle.java new file mode 100644 index 000000000..a844adaa3 --- /dev/null +++ b/remappedSrc/dan200/computercraft/core/apis/handles/EncodedWritableHandle.java @@ -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.core.apis.handles; + +import dan200.computercraft.api.lua.ILuaContext; +import dan200.computercraft.api.lua.LuaException; + +import javax.annotation.Nonnull; +import java.io.BufferedWriter; +import java.io.Closeable; +import java.io.IOException; +import java.nio.channels.Channels; +import java.nio.channels.WritableByteChannel; +import java.nio.charset.Charset; +import java.nio.charset.CharsetEncoder; +import java.nio.charset.CodingErrorAction; +import java.nio.charset.StandardCharsets; + +public class EncodedWritableHandle extends HandleGeneric +{ + private BufferedWriter m_writer; + + public EncodedWritableHandle( @Nonnull BufferedWriter writer, @Nonnull Closeable closable ) + { + super( closable ); + m_writer = writer; + } + + public EncodedWritableHandle( @Nonnull BufferedWriter writer ) + { + this( writer, writer ); + } + + @Nonnull + @Override + public String[] getMethodNames() + { + return new String[] { + "write", + "writeLine", + "flush", + "close", + }; + } + + @Override + public Object[] callMethod( @Nonnull ILuaContext context, int method, @Nonnull Object[] args ) throws LuaException + { + switch( method ) + { + case 0: // write + { + checkOpen(); + String text = args.length > 0 && args[0] != null ? args[0].toString() : ""; + try + { + m_writer.write( text, 0, text.length() ); + return null; + } + catch( IOException e ) + { + throw new LuaException( e.getMessage() ); + } + } + case 1: // writeLine + { + checkOpen(); + String text = args.length > 0 && args[0] != null ? args[0].toString() : ""; + try + { + m_writer.write( text, 0, text.length() ); + m_writer.newLine(); + return null; + } + catch( IOException e ) + { + throw new LuaException( e.getMessage() ); + } + } + case 2: // flush + checkOpen(); + try + { + m_writer.flush(); + return null; + } + catch( IOException e ) + { + return null; + } + case 3: // close + close(); + return null; + default: + return null; + } + } + + + public static BufferedWriter openUtf8( WritableByteChannel channel ) + { + return open( channel, StandardCharsets.UTF_8 ); + } + + public static BufferedWriter open( WritableByteChannel channel, Charset charset ) + { + // Create a charset encoder with the same properties as StreamEncoder does for + // OutputStreams: namely, replace everything instead of erroring. + CharsetEncoder encoder = charset.newEncoder() + .onMalformedInput( CodingErrorAction.REPLACE ) + .onUnmappableCharacter( CodingErrorAction.REPLACE ); + return new BufferedWriter( Channels.newWriter( channel, encoder, -1 ) ); + } +} diff --git a/remappedSrc/dan200/computercraft/core/apis/handles/HandleGeneric.java b/remappedSrc/dan200/computercraft/core/apis/handles/HandleGeneric.java new file mode 100644 index 000000000..2be67444d --- /dev/null +++ b/remappedSrc/dan200/computercraft/core/apis/handles/HandleGeneric.java @@ -0,0 +1,101 @@ +/* + * 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 dan200.computercraft.api.lua.ILuaObject; +import dan200.computercraft.api.lua.LuaException; +import dan200.computercraft.shared.util.IoUtil; + +import javax.annotation.Nonnull; +import java.io.Closeable; +import java.io.IOException; +import java.nio.channels.Channel; +import java.nio.channels.SeekableByteChannel; + +import static dan200.computercraft.core.apis.ArgumentHelper.optLong; +import static dan200.computercraft.core.apis.ArgumentHelper.optString; + +public abstract class HandleGeneric implements ILuaObject +{ + private Closeable m_closable; + private boolean m_open = true; + + protected HandleGeneric( @Nonnull Closeable closable ) + { + m_closable = closable; + } + + protected void checkOpen() throws LuaException + { + if( !m_open ) throw new LuaException( "attempt to use a closed file" ); + } + + protected final void close() + { + m_open = false; + IoUtil.closeQuietly( m_closable ); + m_closable = null; + } + + /** + * Shared implementation for various file handle types + * + * @param channel The channel to seek in + * @param args The Lua arguments to process, like Lua's {@code file:seek}. + * @return The new position of the file, or null if some error occurred. + * @throws LuaException If the arguments were invalid + * @see {@code file:seek} in the Lua manual. + */ + protected static Object[] handleSeek( SeekableByteChannel channel, Object[] args ) throws LuaException + { + try + { + String whence = optString( args, 0, "cur" ); + long offset = optLong( args, 1, 0 ); + switch( whence ) + { + case "set": + channel.position( offset ); + break; + case "cur": + channel.position( channel.position() + offset ); + break; + case "end": + channel.position( channel.size() + offset ); + break; + default: + throw new LuaException( "bad argument #1 to 'seek' (invalid option '" + whence + "'" ); + } + + return new Object[] { channel.position() }; + } + catch( IllegalArgumentException e ) + { + return new Object[] { false, "Position is negative" }; + } + catch( IOException e ) + { + return null; + } + } + + protected static SeekableByteChannel asSeekable( Channel channel ) + { + if( !(channel instanceof SeekableByteChannel) ) return null; + + SeekableByteChannel seekable = (SeekableByteChannel) channel; + try + { + seekable.position( seekable.position() ); + return seekable; + } + catch( IOException | UnsupportedOperationException e ) + { + return null; + } + } +} diff --git a/remappedSrc/dan200/computercraft/core/apis/http/CheckUrl.java b/remappedSrc/dan200/computercraft/core/apis/http/CheckUrl.java new file mode 100644 index 000000000..0f4d3e9c0 --- /dev/null +++ b/remappedSrc/dan200/computercraft/core/apis/http/CheckUrl.java @@ -0,0 +1,65 @@ +/* + * 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.http; + +import dan200.computercraft.core.apis.IAPIEnvironment; + +import java.net.URI; +import java.util.concurrent.Future; + +/** + * Checks a URL using {@link NetworkUtils#getAddress(String, int, boolean)}} + * + * This requires a DNS lookup, and so needs to occur off-thread. + */ +public class CheckUrl extends Resource +{ + private static final String EVENT = "http_check"; + + private Future future; + + private final IAPIEnvironment environment; + private final String address; + private final String host; + + public CheckUrl( ResourceGroup limiter, IAPIEnvironment environment, String address, URI uri ) + { + super( limiter ); + this.environment = environment; + this.address = address; + host = uri.getHost(); + } + + public void run() + { + if( isClosed() ) return; + future = NetworkUtils.EXECUTOR.submit( this::doRun ); + checkClosed(); + } + + private void doRun() + { + if( isClosed() ) return; + + try + { + NetworkUtils.getAddress( host, 80, false ); + if( tryClose() ) environment.queueEvent( EVENT, new Object[] { address, true } ); + } + catch( HTTPRequestException e ) + { + if( tryClose() ) environment.queueEvent( EVENT, new Object[] { address, false, e.getMessage() } ); + } + } + + @Override + protected void dispose() + { + super.dispose(); + future = closeFuture( future ); + } +} diff --git a/remappedSrc/dan200/computercraft/core/apis/http/HTTPRequestException.java b/remappedSrc/dan200/computercraft/core/apis/http/HTTPRequestException.java new file mode 100644 index 000000000..248cbc4c1 --- /dev/null +++ b/remappedSrc/dan200/computercraft/core/apis/http/HTTPRequestException.java @@ -0,0 +1,23 @@ +/* + * 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.http; + +public class HTTPRequestException extends Exception +{ + private static final long serialVersionUID = 7591208619422744652L; + + public HTTPRequestException( String s ) + { + super( s ); + } + + @Override + public Throwable fillInStackTrace() + { + return this; + } +} diff --git a/remappedSrc/dan200/computercraft/core/apis/http/NetworkUtils.java b/remappedSrc/dan200/computercraft/core/apis/http/NetworkUtils.java new file mode 100644 index 000000000..bbb3e754d --- /dev/null +++ b/remappedSrc/dan200/computercraft/core/apis/http/NetworkUtils.java @@ -0,0 +1,154 @@ +/* + * 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.http; + +import dan200.computercraft.ComputerCraft; +import dan200.computercraft.shared.util.ThreadUtils; +import io.netty.buffer.ByteBuf; +import io.netty.channel.EventLoopGroup; +import io.netty.channel.nio.NioEventLoopGroup; +import io.netty.handler.ssl.SslContext; +import io.netty.handler.ssl.SslContextBuilder; + +import javax.net.ssl.SSLException; +import javax.net.ssl.TrustManagerFactory; +import java.net.InetAddress; +import java.net.InetSocketAddress; +import java.security.KeyStore; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.SynchronousQueue; +import java.util.concurrent.ThreadPoolExecutor; +import java.util.concurrent.TimeUnit; + +/** + * Just a shared object for executing simple HTTP related tasks. + */ +public final class NetworkUtils +{ + public static final ExecutorService EXECUTOR = new ThreadPoolExecutor( + 4, Integer.MAX_VALUE, + 60L, TimeUnit.SECONDS, + new SynchronousQueue<>(), + ThreadUtils.builder( "Network" ) + .setPriority( Thread.MIN_PRIORITY + (Thread.NORM_PRIORITY - Thread.MIN_PRIORITY) / 2 ) + .build() + ); + + public static final EventLoopGroup LOOP_GROUP = new NioEventLoopGroup( 4, ThreadUtils.builder( "Netty" ) + .setPriority( Thread.MIN_PRIORITY + (Thread.NORM_PRIORITY - Thread.MIN_PRIORITY) / 2 ) + .build() + ); + + private NetworkUtils() + { + } + + private static final Object sslLock = new Object(); + private static TrustManagerFactory trustManager; + private static SslContext sslContext; + private static boolean triedSslContext = false; + + private static TrustManagerFactory getTrustManager() + { + if( trustManager != null ) return trustManager; + synchronized( sslLock ) + { + if( trustManager != null ) return trustManager; + + TrustManagerFactory tmf = null; + try + { + tmf = TrustManagerFactory.getInstance( TrustManagerFactory.getDefaultAlgorithm() ); + tmf.init( (KeyStore) null ); + } + catch( Exception e ) + { + ComputerCraft.log.error( "Cannot setup trust manager", e ); + } + + return trustManager = tmf; + } + } + + public static SslContext getSslContext() throws HTTPRequestException + { + if( sslContext != null || triedSslContext ) return sslContext; + synchronized( sslLock ) + { + if( sslContext != null || triedSslContext ) return sslContext; + try + { + return sslContext = SslContextBuilder + .forClient() + .trustManager( getTrustManager() ) + .build(); + } + catch( SSLException e ) + { + ComputerCraft.log.error( "Cannot construct SSL context", e ); + triedSslContext = true; + sslContext = null; + + throw new HTTPRequestException( "Cannot create a secure connection" ); + } + } + } + + /** + * Checks a host is allowed + * + * @param host The domain to check against + * @throws HTTPRequestException If the host is not permitted. + */ + public static void checkHost( String host ) throws HTTPRequestException + { + if( !ComputerCraft.http_whitelist.matches( host ) || ComputerCraft.http_blacklist.matches( host ) ) + { + throw new HTTPRequestException( "Domain not permitted" ); + } + } + + /** + * Create a {@link InetSocketAddress} from the resolved {@code host} and port. + * + * Note, this may require a DNS lookup, and so should not be executed on the main CC thread. + * + * @param host The host to resolve. + * @param port The port, or -1 if not defined. + * @param ssl Whether to connect with SSL. This is used to find the default port if not otherwise specified. + * @return The resolved address. + * @throws HTTPRequestException If the host is not permitted. + */ + public static InetSocketAddress getAddress( String host, int port, boolean ssl ) throws HTTPRequestException + { + if( port < 0 ) port = ssl ? 443 : 80; + + InetSocketAddress socketAddress = new InetSocketAddress( host, port ); + if( socketAddress.isUnresolved() ) throw new HTTPRequestException( "Unknown host" ); + + InetAddress address = socketAddress.getAddress(); + if( !ComputerCraft.http_whitelist.matches( address ) || ComputerCraft.http_blacklist.matches( address ) ) + { + throw new HTTPRequestException( "Domain not permitted" ); + } + + return socketAddress; + } + + /** + * Read a {@link ByteBuf} into a byte array. + * + * @param buffer The buffer to read. + * @return The resulting bytes. + */ + public static byte[] toBytes( ByteBuf buffer ) + { + byte[] bytes = new byte[buffer.readableBytes()]; + buffer.readBytes( bytes ); + return bytes; + } +} diff --git a/remappedSrc/dan200/computercraft/core/apis/http/Resource.java b/remappedSrc/dan200/computercraft/core/apis/http/Resource.java new file mode 100644 index 000000000..60a6c0590 --- /dev/null +++ b/remappedSrc/dan200/computercraft/core/apis/http/Resource.java @@ -0,0 +1,145 @@ +/* + * 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.http; + +import dan200.computercraft.shared.util.IoUtil; +import io.netty.channel.Channel; +import io.netty.channel.ChannelFuture; + +import java.io.Closeable; +import java.lang.ref.Reference; +import java.lang.ref.ReferenceQueue; +import java.lang.ref.WeakReference; +import java.util.concurrent.Future; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.function.Consumer; + +/** + * A holder for one or more resources, with a lifetime. + */ +public abstract class Resource> implements Closeable +{ + private final AtomicBoolean closed = new AtomicBoolean( false ); + private final ResourceGroup limiter; + + protected Resource( ResourceGroup limiter ) + { + this.limiter = limiter; + } + + /** + * Whether this resource is closed. + * + * @return Whether this resource is closed. + */ + public final boolean isClosed() + { + return closed.get(); + } + + /** + * Checks if this has been cancelled. If so, it'll clean up any + * existing resources and cancel any pending futures. + */ + public final boolean checkClosed() + { + if( !closed.get() ) return false; + dispose(); + return true; + } + + /** + * Try to close the current resource. + * + * @return Whether this was successfully closed, or {@code false} if it has already been closed. + */ + protected final boolean tryClose() + { + if( closed.getAndSet( true ) ) return false; + dispose(); + return true; + } + + /** + * Clean up any pending resources + * + * Note, this may be called multiple times, and so should be thread-safe and + * avoid any major side effects. + */ + protected void dispose() + { + @SuppressWarnings( "unchecked" ) T thisT = (T) this; + limiter.release( thisT ); + } + + /** + * Create a {@link WeakReference} which will close {@code this} when collected. + * + * @param object The object to reference to + * @return The weak reference. + */ + protected WeakReference createOwnerReference( R object ) + { + return new CloseReference<>( this, object ); + } + + @Override + public final void close() + { + tryClose(); + } + + public boolean queue( Consumer task ) + { + @SuppressWarnings( "unchecked" ) T thisT = (T) this; + return limiter.queue( thisT, () -> task.accept( thisT ) ); + } + + protected static T closeCloseable( T closeable ) + { + if( closeable != null ) IoUtil.closeQuietly( closeable ); + return null; + } + + protected static ChannelFuture closeChannel( ChannelFuture future ) + { + if( future != null ) + { + future.cancel( false ); + Channel channel = future.channel(); + if( channel != null && channel.isOpen() ) channel.close(); + } + + return null; + } + + protected static > T closeFuture( T future ) + { + if( future != null ) future.cancel( true ); + return null; + } + + + private static final ReferenceQueue QUEUE = new ReferenceQueue<>(); + + private static class CloseReference extends WeakReference + { + final Resource resource; + + CloseReference( Resource resource, T referent ) + { + super( referent, QUEUE ); + this.resource = resource; + } + } + + public static void cleanup() + { + Reference reference; + while( (reference = QUEUE.poll()) != null ) ((CloseReference) reference).resource.close(); + } +} diff --git a/remappedSrc/dan200/computercraft/core/apis/http/ResourceGroup.java b/remappedSrc/dan200/computercraft/core/apis/http/ResourceGroup.java new file mode 100644 index 000000000..598d7f336 --- /dev/null +++ b/remappedSrc/dan200/computercraft/core/apis/http/ResourceGroup.java @@ -0,0 +1,81 @@ +/* + * 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.http; + +import java.util.Collections; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; +import java.util.function.IntSupplier; +import java.util.function.Supplier; + +/** + * A collection of {@link Resource}s, with an upper bound on capacity. + */ +public class ResourceGroup> +{ + private static final IntSupplier ZERO = () -> 0; + + final IntSupplier limit; + + boolean active = false; + + final Set resources = Collections.newSetFromMap( new ConcurrentHashMap<>() ); + + public ResourceGroup( IntSupplier limit ) + { + this.limit = limit; + } + + public ResourceGroup() + { + limit = ZERO; + } + + public void startup() + { + active = true; + } + + public synchronized void shutdown() + { + active = false; + + for( T resource : resources ) resource.close(); + resources.clear(); + + Resource.cleanup(); + } + + + public final boolean queue( T resource, Runnable setup ) + { + return queue( () -> { + setup.run(); + return resource; + } ); + } + + public synchronized boolean queue( Supplier resource ) + { + Resource.cleanup(); + if( !active ) return false; + + int limit = this.limit.getAsInt(); + if( limit <= 0 || resources.size() < limit ) + { + resources.add( resource.get() ); + return true; + } + + return false; + } + + public synchronized void release( T resource ) + { + resources.remove( resource ); + } +} diff --git a/remappedSrc/dan200/computercraft/core/apis/http/ResourceQueue.java b/remappedSrc/dan200/computercraft/core/apis/http/ResourceQueue.java new file mode 100644 index 000000000..9ac8eb56f --- /dev/null +++ b/remappedSrc/dan200/computercraft/core/apis/http/ResourceQueue.java @@ -0,0 +1,59 @@ +/* + * 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.http; + +import java.util.ArrayDeque; +import java.util.function.IntSupplier; +import java.util.function.Supplier; + +/** + * A {@link ResourceGroup} which will queue items when the group at capacity. + */ +public class ResourceQueue> extends ResourceGroup +{ + private final ArrayDeque> pending = new ArrayDeque<>(); + + public ResourceQueue( IntSupplier limit ) + { + super( limit ); + } + + public ResourceQueue() + { + } + + @Override + public synchronized void shutdown() + { + super.shutdown(); + pending.clear(); + } + + @Override + public synchronized boolean queue( Supplier resource ) + { + if( !active ) return false; + + if( !super.queue( resource ) ) pending.add( resource ); + return true; + } + + @Override + public synchronized void release( T resource ) + { + super.release( resource ); + + if( !active ) return; + + int limit = this.limit.getAsInt(); + if( limit <= 0 || resources.size() < limit ) + { + Supplier next = pending.poll(); + if( next != null ) resources.add( next.get() ); + } + } +} diff --git a/remappedSrc/dan200/computercraft/core/apis/http/request/HttpRequest.java b/remappedSrc/dan200/computercraft/core/apis/http/request/HttpRequest.java new file mode 100644 index 000000000..d26e33b66 --- /dev/null +++ b/remappedSrc/dan200/computercraft/core/apis/http/request/HttpRequest.java @@ -0,0 +1,278 @@ +/* + * 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.http.request; + +import dan200.computercraft.ComputerCraft; +import dan200.computercraft.api.lua.ILuaObject; +import dan200.computercraft.core.apis.IAPIEnvironment; +import dan200.computercraft.core.apis.http.HTTPRequestException; +import dan200.computercraft.core.apis.http.NetworkUtils; +import dan200.computercraft.core.apis.http.Resource; +import dan200.computercraft.core.apis.http.ResourceGroup; +import dan200.computercraft.core.tracking.TrackingField; +import io.netty.bootstrap.Bootstrap; +import io.netty.buffer.ByteBuf; +import io.netty.buffer.Unpooled; +import io.netty.channel.ChannelFuture; +import io.netty.channel.ChannelInitializer; +import io.netty.channel.ChannelPipeline; +import io.netty.channel.ConnectTimeoutException; +import io.netty.channel.socket.SocketChannel; +import io.netty.channel.socket.nio.NioSocketChannel; +import io.netty.handler.codec.TooLongFrameException; +import io.netty.handler.codec.http.*; +import io.netty.handler.ssl.SslContext; +import io.netty.handler.timeout.ReadTimeoutException; +import io.netty.handler.timeout.ReadTimeoutHandler; + +import java.net.InetSocketAddress; +import java.net.URI; +import java.net.URISyntaxException; +import java.nio.charset.StandardCharsets; +import java.util.Locale; +import java.util.Map; +import java.util.concurrent.Future; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicInteger; + +/** + * Represents one or more + */ +public class HttpRequest extends Resource +{ + private static final String SUCCESS_EVENT = "http_success"; + private static final String FAILURE_EVENT = "http_failure"; + + private static final int MAX_REDIRECTS = 16; + + private Future executorFuture; + private ChannelFuture connectFuture; + private HttpRequestHandler currentRequest; + + private final IAPIEnvironment environment; + + private final String address; + private final ByteBuf postBuffer; + private final HttpHeaders headers; + private final boolean binary; + + final AtomicInteger redirects; + + public HttpRequest( ResourceGroup limiter, IAPIEnvironment environment, String address, String postText, HttpHeaders headers, boolean binary, boolean followRedirects ) + { + super( limiter ); + this.environment = environment; + this.address = address; + postBuffer = postText != null + ? Unpooled.wrappedBuffer( postText.getBytes( StandardCharsets.UTF_8 ) ) + : Unpooled.buffer( 0 ); + this.headers = headers; + this.binary = binary; + redirects = new AtomicInteger( followRedirects ? MAX_REDIRECTS : 0 ); + + if( postText != null ) + { + if( !headers.contains( HttpHeaderNames.CONTENT_TYPE ) ) + { + headers.set( HttpHeaderNames.CONTENT_TYPE, "application/x-www-form-urlencoded; charset=utf-8" ); + } + + if( !headers.contains( HttpHeaderNames.CONTENT_LENGTH ) ) + { + headers.set( HttpHeaderNames.CONTENT_LENGTH, postBuffer.readableBytes() ); + } + } + } + + public IAPIEnvironment environment() + { + return environment; + } + + public static URI checkUri( String address ) throws HTTPRequestException + { + URI url; + try + { + url = new URI( address ); + } + catch( URISyntaxException e ) + { + throw new HTTPRequestException( "URL malformed" ); + } + + checkUri( url ); + return url; + } + + public static void checkUri( URI url ) throws HTTPRequestException + { + // Validate the URL + if( url.getScheme() == null ) throw new HTTPRequestException( "Must specify http or https" ); + if( url.getHost() == null ) throw new HTTPRequestException( "URL malformed" ); + + String scheme = url.getScheme().toLowerCase( Locale.ROOT ); + if( !scheme.equalsIgnoreCase( "http" ) && !scheme.equalsIgnoreCase( "https" ) ) + { + throw new HTTPRequestException( "Invalid protocol '" + scheme + "'" ); + } + + NetworkUtils.checkHost( url.getHost() ); + } + + public void request( URI uri, HttpMethod method ) + { + if( isClosed() ) return; + executorFuture = NetworkUtils.EXECUTOR.submit( () -> doRequest( uri, method ) ); + checkClosed(); + } + + private void doRequest( URI uri, HttpMethod method ) + { + // If we're cancelled, abort. + if( isClosed() ) return; + + try + { + boolean ssl = uri.getScheme().equalsIgnoreCase( "https" ); + InetSocketAddress socketAddress = NetworkUtils.getAddress( uri.getHost(), uri.getPort(), ssl ); + SslContext sslContext = ssl ? NetworkUtils.getSslContext() : null; + + // getAddress may have a slight delay, so let's perform another cancellation check. + if( isClosed() ) return; + + // Add request size to the tracker before opening the connection + environment.addTrackingChange( TrackingField.HTTP_REQUESTS, 1 ); + environment.addTrackingChange( TrackingField.HTTP_UPLOAD, getHeaderSize( headers ) + postBuffer.capacity() ); + + HttpRequestHandler handler = currentRequest = new HttpRequestHandler( this, uri, method ); + connectFuture = new Bootstrap() + .group( NetworkUtils.LOOP_GROUP ) + .channelFactory( NioSocketChannel::new ) + .handler( new ChannelInitializer() + { + @Override + protected void initChannel( SocketChannel ch ) + { + + if( ComputerCraft.httpTimeout > 0 ) + { + ch.config().setConnectTimeoutMillis( ComputerCraft.httpTimeout ); + } + + ChannelPipeline p = ch.pipeline(); + if( sslContext != null ) + { + p.addLast( sslContext.newHandler( ch.alloc(), uri.getHost(), socketAddress.getPort() ) ); + } + + if( ComputerCraft.httpTimeout > 0 ) + { + p.addLast( new ReadTimeoutHandler( ComputerCraft.httpTimeout, TimeUnit.MILLISECONDS ) ); + } + + p.addLast( + new HttpClientCodec(), + new HttpContentDecompressor(), + handler + ); + } + } ) + .remoteAddress( socketAddress ) + .connect() + .addListener( c -> { + if( !c.isSuccess() ) failure( c.cause() ); + } ); + + // Do an additional check for cancellation + checkClosed(); + } + catch( HTTPRequestException e ) + { + failure( e.getMessage() ); + } + catch( Exception e ) + { + failure( "Could not connect" ); + if( ComputerCraft.logPeripheralErrors ) ComputerCraft.log.error( "Error in HTTP request", e ); + } + } + + void failure( String message ) + { + if( tryClose() ) environment.queueEvent( FAILURE_EVENT, new Object[] { address, message } ); + } + + void failure( Throwable cause ) + { + String message; + if( cause instanceof HTTPRequestException ) + { + message = cause.getMessage(); + } + else if( cause instanceof TooLongFrameException ) + { + message = "Response is too large"; + } + else if( cause instanceof ReadTimeoutException || cause instanceof ConnectTimeoutException ) + { + message = "Timed out"; + } + else + { + message = "Could not connect"; + } + + failure( message ); + } + + void failure( String message, ILuaObject object ) + { + if( tryClose() ) environment.queueEvent( FAILURE_EVENT, new Object[] { address, message, object } ); + } + + void success( ILuaObject object ) + { + if( tryClose() ) environment.queueEvent( SUCCESS_EVENT, new Object[] { address, object } ); + } + + @Override + protected void dispose() + { + super.dispose(); + + executorFuture = closeFuture( executorFuture ); + connectFuture = closeChannel( connectFuture ); + currentRequest = closeCloseable( currentRequest ); + } + + public static long getHeaderSize( HttpHeaders headers ) + { + long size = 0; + for( Map.Entry header : headers ) + { + size += header.getKey() == null ? 0 : header.getKey().length(); + size += header.getValue() == null ? 0 : header.getValue().length() + 1; + } + return size; + } + + public ByteBuf body() + { + return postBuffer; + } + + public HttpHeaders headers() + { + return headers; + } + + public boolean isBinary() + { + return binary; + } +} diff --git a/remappedSrc/dan200/computercraft/core/apis/http/request/HttpRequestHandler.java b/remappedSrc/dan200/computercraft/core/apis/http/request/HttpRequestHandler.java new file mode 100644 index 000000000..a76a82621 --- /dev/null +++ b/remappedSrc/dan200/computercraft/core/apis/http/request/HttpRequestHandler.java @@ -0,0 +1,259 @@ +/* + * 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.http.request; + +import dan200.computercraft.ComputerCraft; +import dan200.computercraft.api.lua.ILuaObject; +import dan200.computercraft.core.apis.handles.ArrayByteChannel; +import dan200.computercraft.core.apis.handles.BinaryReadableHandle; +import dan200.computercraft.core.apis.handles.EncodedReadableHandle; +import dan200.computercraft.core.apis.http.HTTPRequestException; +import dan200.computercraft.core.apis.http.NetworkUtils; +import dan200.computercraft.core.tracking.TrackingField; +import io.netty.buffer.ByteBuf; +import io.netty.buffer.CompositeByteBuf; +import io.netty.channel.ChannelHandlerContext; +import io.netty.channel.SimpleChannelInboundHandler; +import io.netty.handler.codec.http.*; + +import java.io.Closeable; +import java.io.UnsupportedEncodingException; +import java.net.URI; +import java.net.URISyntaxException; +import java.net.URLDecoder; +import java.nio.charset.Charset; +import java.nio.charset.StandardCharsets; +import java.util.HashMap; +import java.util.Map; + +import static dan200.computercraft.core.apis.http.request.HttpRequest.getHeaderSize; + +public final class HttpRequestHandler extends SimpleChannelInboundHandler implements Closeable +{ + /** + * Same as {@link io.netty.handler.codec.MessageAggregator}. + */ + private static final int DEFAULT_MAX_COMPOSITE_BUFFER_COMPONENTS = 1024; + + private static final byte[] EMPTY_BYTES = new byte[0]; + + private final HttpRequest request; + private boolean closed = false; + + private final URI uri; + private final HttpMethod method; + + private Charset responseCharset; + private final HttpHeaders responseHeaders = new DefaultHttpHeaders(); + private HttpResponseStatus responseStatus; + private CompositeByteBuf responseBody; + + HttpRequestHandler( HttpRequest request, URI uri, HttpMethod method ) + { + this.request = request; + + this.uri = uri; + this.method = method; + } + + @Override + public void channelActive( ChannelHandlerContext ctx ) throws Exception + { + if( request.checkClosed() ) return; + + ByteBuf body = request.body(); + body.resetReaderIndex().retain(); + + String requestUri = uri.getRawPath(); + if( uri.getRawQuery() != null ) requestUri += "?" + uri.getRawQuery(); + + FullHttpRequest request = new DefaultFullHttpRequest( HttpVersion.HTTP_1_1, HttpMethod.GET, requestUri, body ); + request.setMethod( method ); + request.headers().set( this.request.headers() ); + + // We force some headers to be always applied + if( !request.headers().contains( HttpHeaderNames.ACCEPT_CHARSET ) ) + { + request.headers().set( HttpHeaderNames.ACCEPT_CHARSET, "UTF-8" ); + } + if( !request.headers().contains( HttpHeaderNames.USER_AGENT ) ) + { + request.headers().set( HttpHeaderNames.USER_AGENT, ComputerCraft.MOD_ID + "/" + ComputerCraft.getVersion() ); + } + request.headers().set( HttpHeaderNames.HOST, uri.getHost() ); + request.headers().set( HttpHeaderNames.CONNECTION, HttpHeaderValues.CLOSE ); + + ctx.channel().writeAndFlush( request ); + + super.channelActive( ctx ); + } + + @Override + public void channelInactive( ChannelHandlerContext ctx ) throws Exception + { + if( !closed ) request.failure( "Could not connect" ); + super.channelInactive( ctx ); + } + + @Override + public void channelRead0( ChannelHandlerContext ctx, HttpObject message ) + { + if( closed || request.checkClosed() ) return; + + if( message instanceof HttpResponse ) + { + HttpResponse response = (HttpResponse) message; + + if( request.redirects.get() > 0 ) + { + URI redirect = getRedirect( response.status(), response.headers() ); + if( redirect != null && !uri.equals( redirect ) && request.redirects.getAndDecrement() > 0 ) + { + // If we have a redirect, and don't end up at the same place, then follow it. + + // We mark ourselves as disposed first though, to avoid firing events when the channel + // becomes inactive or disposed. + closed = true; + ctx.close(); + + try + { + HttpRequest.checkUri( redirect ); + } + catch( HTTPRequestException e ) + { + // If we cannot visit this uri, then fail. + request.failure( e.getMessage() ); + return; + } + + request.request( redirect, response.status().code() == 303 ? HttpMethod.GET : method ); + return; + } + } + + responseCharset = HttpUtil.getCharset( response, StandardCharsets.UTF_8 ); + responseStatus = response.status(); + responseHeaders.add( response.headers() ); + } + + if( message instanceof HttpContent ) + { + HttpContent content = (HttpContent) message; + + if( responseBody == null ) + { + responseBody = ctx.alloc().compositeBuffer( DEFAULT_MAX_COMPOSITE_BUFFER_COMPONENTS ); + } + + ByteBuf partial = content.content(); + if( partial.isReadable() ) + { + // If we've read more than we're allowed to handle, abort as soon as possible. + if( ComputerCraft.httpMaxDownload != 0 && responseBody.readableBytes() + partial.readableBytes() > ComputerCraft.httpMaxDownload ) + { + closed = true; + ctx.close(); + + request.failure( "Response is too large" ); + return; + } + + responseBody.addComponent( true, partial.retain() ); + } + + if( message instanceof LastHttpContent ) + { + LastHttpContent last = (LastHttpContent) message; + responseHeaders.add( last.trailingHeaders() ); + + // Set the content length, if not already given. + if( responseHeaders.contains( HttpHeaderNames.CONTENT_LENGTH ) ) + { + responseHeaders.set( HttpHeaderNames.CONTENT_LENGTH, responseBody.readableBytes() ); + } + + ctx.close(); + sendResponse(); + } + } + } + + @Override + public void exceptionCaught( ChannelHandlerContext ctx, Throwable cause ) + { + if( ComputerCraft.logPeripheralErrors ) ComputerCraft.log.error( "Error handling HTTP response", cause ); + request.failure( cause ); + } + + private void sendResponse() + { + // Read the ByteBuf into a channel. + CompositeByteBuf body = responseBody; + byte[] bytes = body == null ? EMPTY_BYTES : NetworkUtils.toBytes( body ); + + // Decode the headers + HttpResponseStatus status = responseStatus; + Map headers = new HashMap<>(); + for( Map.Entry header : responseHeaders ) + { + String existing = headers.get( header.getKey() ); + headers.put( header.getKey(), existing == null ? header.getValue() : existing + "," + header.getValue() ); + } + + // Fire off a stats event + request.environment().addTrackingChange( TrackingField.HTTP_DOWNLOAD, getHeaderSize( responseHeaders ) + bytes.length ); + + // Prepare to queue an event + ArrayByteChannel contents = new ArrayByteChannel( bytes ); + final ILuaObject reader = request.isBinary() + ? new BinaryReadableHandle( contents ) + : new EncodedReadableHandle( EncodedReadableHandle.open( contents, responseCharset ) ); + ILuaObject stream = new HttpResponseHandle( reader, status.code(), status.reasonPhrase(), headers ); + + if( status.code() >= 200 && status.code() < 400 ) + { + request.success( stream ); + } + else + { + request.failure( status.reasonPhrase(), stream ); + } + } + + /** + * Determine the redirect from this response + */ + private URI getRedirect( HttpResponseStatus status, HttpHeaders headers ) + { + int code = status.code(); + if( code < 300 || code > 307 || code == 304 || code == 306 ) return null; + + String location = headers.get( HttpHeaderNames.LOCATION ); + if( location == null ) return null; + + try + { + return uri.resolve( new URI( URLDecoder.decode( location, "UTF-8" ) ) ); + } + catch( UnsupportedEncodingException | IllegalArgumentException | URISyntaxException e ) + { + return null; + } + } + + @Override + public void close() + { + closed = true; + if( responseBody != null ) + { + responseBody.release(); + responseBody = null; + } + } +} diff --git a/remappedSrc/dan200/computercraft/core/apis/http/request/HttpResponseHandle.java b/remappedSrc/dan200/computercraft/core/apis/http/request/HttpResponseHandle.java new file mode 100644 index 000000000..49add574c --- /dev/null +++ b/remappedSrc/dan200/computercraft/core/apis/http/request/HttpResponseHandle.java @@ -0,0 +1,67 @@ +/* + * 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.http.request; + +import dan200.computercraft.api.lua.ILuaContext; +import dan200.computercraft.api.lua.ILuaObject; +import dan200.computercraft.api.lua.LuaException; + +import javax.annotation.Nonnull; +import java.util.Arrays; +import java.util.Map; + +/** + * Wraps a {@link dan200.computercraft.core.apis.handles.HandleGeneric} and provides additional methods for + * getting the response code and headers. + */ +public class HttpResponseHandle implements ILuaObject +{ + private final String[] newMethods; + private final int methodOffset; + private final ILuaObject reader; + private final int responseCode; + private final String responseStatus; + private final Map responseHeaders; + + public HttpResponseHandle( @Nonnull ILuaObject reader, int responseCode, String responseStatus, @Nonnull Map responseHeaders ) + { + this.reader = reader; + this.responseCode = responseCode; + this.responseStatus = responseStatus; + this.responseHeaders = responseHeaders; + + String[] oldMethods = reader.getMethodNames(); + final int methodOffset = this.methodOffset = oldMethods.length; + + final String[] newMethods = this.newMethods = Arrays.copyOf( oldMethods, oldMethods.length + 2 ); + newMethods[methodOffset + 0] = "getResponseCode"; + newMethods[methodOffset + 1] = "getResponseHeaders"; + } + + @Nonnull + @Override + public String[] getMethodNames() + { + return newMethods; + } + + @Override + public Object[] callMethod( @Nonnull ILuaContext context, int method, @Nonnull Object[] args ) throws LuaException, InterruptedException + { + if( method < methodOffset ) return reader.callMethod( context, method, args ); + + switch( method - methodOffset ) + { + case 0: // getResponseCode + return new Object[] { responseCode, responseStatus }; + case 1: // getResponseHeaders + return new Object[] { responseHeaders }; + default: + return null; + } + } +} diff --git a/remappedSrc/dan200/computercraft/core/apis/http/websocket/Websocket.java b/remappedSrc/dan200/computercraft/core/apis/http/websocket/Websocket.java new file mode 100644 index 000000000..7698c3d18 --- /dev/null +++ b/remappedSrc/dan200/computercraft/core/apis/http/websocket/Websocket.java @@ -0,0 +1,234 @@ +/* + * 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.http.websocket; + +import com.google.common.base.Strings; +import dan200.computercraft.ComputerCraft; +import dan200.computercraft.core.apis.IAPIEnvironment; +import dan200.computercraft.core.apis.http.HTTPRequestException; +import dan200.computercraft.core.apis.http.NetworkUtils; +import dan200.computercraft.core.apis.http.Resource; +import dan200.computercraft.core.apis.http.ResourceGroup; +import dan200.computercraft.shared.util.IoUtil; +import io.netty.bootstrap.Bootstrap; +import io.netty.channel.Channel; +import io.netty.channel.ChannelFuture; +import io.netty.channel.ChannelInitializer; +import io.netty.channel.ChannelPipeline; +import io.netty.channel.socket.SocketChannel; +import io.netty.channel.socket.nio.NioSocketChannel; +import io.netty.handler.codec.http.HttpClientCodec; +import io.netty.handler.codec.http.HttpHeaders; +import io.netty.handler.codec.http.HttpObjectAggregator; +import io.netty.handler.codec.http.websocketx.WebSocketClientHandshaker; +import io.netty.handler.codec.http.websocketx.WebSocketClientHandshakerFactory; +import io.netty.handler.codec.http.websocketx.WebSocketVersion; +import io.netty.handler.codec.http.websocketx.extensions.compression.WebSocketClientCompressionHandler; +import io.netty.handler.ssl.SslContext; + +import java.lang.ref.WeakReference; +import java.net.InetSocketAddress; +import java.net.URI; +import java.net.URISyntaxException; +import java.util.concurrent.Future; + +/** + * Provides functionality to verify and connect to a remote websocket. + */ +public class Websocket extends Resource +{ + public static final int MAX_MESSAGE_SIZE = 64 * 1024; + + static final String SUCCESS_EVENT = "websocket_success"; + static final String FAILURE_EVENT = "websocket_failure"; + static final String CLOSE_EVENT = "websocket_closed"; + static final String MESSAGE_EVENT = "websocket_message"; + + private Future executorFuture; + private ChannelFuture connectFuture; + private WeakReference websocketHandle; + + private final IAPIEnvironment environment; + private final URI uri; + private final String address; + private final HttpHeaders headers; + + public Websocket( ResourceGroup limiter, IAPIEnvironment environment, URI uri, String address, HttpHeaders headers ) + { + super( limiter ); + this.environment = environment; + this.uri = uri; + this.address = address; + this.headers = headers; + } + + public static URI checkUri( String address ) throws HTTPRequestException + { + URI uri = null; + try + { + uri = new URI( address ); + } + catch( URISyntaxException ignored ) + { + } + + if( uri == null || uri.getHost() == null ) + { + try + { + uri = new URI( "ws://" + address ); + } + catch( URISyntaxException ignored ) + { + } + } + + if( uri == null || uri.getHost() == null ) throw new HTTPRequestException( "URL malformed" ); + + String scheme = uri.getScheme(); + if( scheme == null ) + { + try + { + uri = new URI( "ws://" + uri ); + } + catch( URISyntaxException e ) + { + throw new HTTPRequestException( "URL malformed" ); + } + } + else if( !scheme.equalsIgnoreCase( "wss" ) && !scheme.equalsIgnoreCase( "ws" ) ) + { + throw new HTTPRequestException( "Invalid scheme '" + scheme + "'" ); + } + + NetworkUtils.checkHost( uri.getHost() ); + return uri; + } + + public void connect() + { + if( isClosed() ) return; + executorFuture = NetworkUtils.EXECUTOR.submit( this::doConnect ); + checkClosed(); + } + + private void doConnect() + { + // If we're cancelled, abort. + if( isClosed() ) return; + + try + { + boolean ssl = uri.getScheme().equalsIgnoreCase( "wss" ); + + InetSocketAddress socketAddress = NetworkUtils.getAddress( uri.getHost(), uri.getPort(), ssl ); + SslContext sslContext = ssl ? NetworkUtils.getSslContext() : null; + + // getAddress may have a slight delay, so let's perform another cancellation check. + if( isClosed() ) return; + + connectFuture = new Bootstrap() + .group( NetworkUtils.LOOP_GROUP ) + .channel( NioSocketChannel.class ) + .handler( new ChannelInitializer() + { + @Override + protected void initChannel( SocketChannel ch ) + { + ChannelPipeline p = ch.pipeline(); + if( sslContext != null ) + { + p.addLast( sslContext.newHandler( ch.alloc(), uri.getHost(), socketAddress.getPort() ) ); + } + + WebSocketClientHandshaker handshaker = WebSocketClientHandshakerFactory.newHandshaker( + uri, WebSocketVersion.V13, null, true, headers, + ComputerCraft.httpMaxWebsocketMessage == 0 ? MAX_MESSAGE_SIZE : ComputerCraft.httpMaxWebsocketMessage + ); + + p.addLast( + new HttpClientCodec(), + new HttpObjectAggregator( 8192 ), + WebSocketClientCompressionHandler.INSTANCE, + new WebsocketHandler( Websocket.this, handshaker ) + ); + } + } ) + .remoteAddress( socketAddress ) + .connect() + .addListener( c -> { + if( !c.isSuccess() ) failure( c.cause().getMessage() ); + } ); + + // Do an additional check for cancellation + checkClosed(); + } + catch( HTTPRequestException e ) + { + failure( e.getMessage() ); + } + catch( Exception e ) + { + failure( "Could not connect" ); + if( ComputerCraft.logPeripheralErrors ) ComputerCraft.log.error( "Error in websocket", e ); + } + } + + void success( Channel channel ) + { + if( isClosed() ) return; + + WebsocketHandle handle = new WebsocketHandle( this, channel ); + environment().queueEvent( SUCCESS_EVENT, new Object[] { address, handle } ); + websocketHandle = createOwnerReference( handle ); + + checkClosed(); + } + + void failure( String message ) + { + if( tryClose() ) environment.queueEvent( FAILURE_EVENT, new Object[] { address, message } ); + } + + void close( int status, String reason ) + { + if( tryClose() ) + { + environment.queueEvent( CLOSE_EVENT, new Object[] { + address, + Strings.isNullOrEmpty( reason ) ? null : reason, + status < 0 ? null : status, + } ); + } + } + + @Override + protected void dispose() + { + super.dispose(); + + executorFuture = closeFuture( executorFuture ); + connectFuture = closeChannel( connectFuture ); + + WeakReference websocketHandleRef = websocketHandle; + WebsocketHandle websocketHandle = websocketHandleRef == null ? null : websocketHandleRef.get(); + if( websocketHandle != null ) IoUtil.closeQuietly( websocketHandle ); + this.websocketHandle = null; + } + + public IAPIEnvironment environment() + { + return environment; + } + + public String address() + { + return address; + } +} diff --git a/remappedSrc/dan200/computercraft/core/apis/http/websocket/WebsocketHandle.java b/remappedSrc/dan200/computercraft/core/apis/http/websocket/WebsocketHandle.java new file mode 100644 index 000000000..f53a2bff1 --- /dev/null +++ b/remappedSrc/dan200/computercraft/core/apis/http/websocket/WebsocketHandle.java @@ -0,0 +1,121 @@ +/* + * 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.http.websocket; + +import com.google.common.base.Objects; +import dan200.computercraft.ComputerCraft; +import dan200.computercraft.api.lua.ILuaContext; +import dan200.computercraft.api.lua.ILuaObject; +import dan200.computercraft.api.lua.LuaException; +import dan200.computercraft.core.tracking.TrackingField; +import dan200.computercraft.shared.util.StringUtil; +import io.netty.buffer.Unpooled; +import io.netty.channel.Channel; +import io.netty.handler.codec.http.websocketx.BinaryWebSocketFrame; +import io.netty.handler.codec.http.websocketx.TextWebSocketFrame; + +import javax.annotation.Nonnull; +import javax.annotation.Nullable; +import java.io.Closeable; +import java.util.Arrays; + +import static dan200.computercraft.core.apis.ArgumentHelper.optBoolean; +import static dan200.computercraft.core.apis.http.websocket.Websocket.CLOSE_EVENT; +import static dan200.computercraft.core.apis.http.websocket.Websocket.MESSAGE_EVENT; + +public class WebsocketHandle implements ILuaObject, Closeable +{ + private final Websocket websocket; + private boolean closed = false; + + private Channel channel; + + public WebsocketHandle( Websocket websocket, Channel channel ) + { + this.websocket = websocket; + this.channel = channel; + } + + @Nonnull + @Override + public String[] getMethodNames() + { + return new String[] { "receive", "send", "close" }; + } + + @Nullable + @Override + public Object[] callMethod( @Nonnull ILuaContext context, int method, @Nonnull Object[] arguments ) throws LuaException, InterruptedException + { + switch( method ) + { + case 0: // receive + checkOpen(); + while( true ) + { + Object[] event = context.pullEvent( null ); + if( event.length >= 3 && Objects.equal( event[0], MESSAGE_EVENT ) && Objects.equal( event[1], websocket.address() ) ) + { + return Arrays.copyOfRange( event, 2, event.length ); + } + else if( event.length >= 2 && Objects.equal( event[0], CLOSE_EVENT ) && Objects.equal( event[1], websocket.address() ) && closed ) + { + return null; + } + } + + case 1: // send + { + checkOpen(); + + String text = arguments.length > 0 && arguments[0] != null ? arguments[0].toString() : ""; + if( ComputerCraft.httpMaxWebsocketMessage != 0 && text.length() > ComputerCraft.httpMaxWebsocketMessage ) + { + throw new LuaException( "Message is too large" ); + } + + boolean binary = optBoolean( arguments, 1, false ); + websocket.environment().addTrackingChange( TrackingField.WEBSOCKET_OUTGOING, text.length() ); + + Channel channel = this.channel; + if( channel != null ) + { + channel.writeAndFlush( binary + ? new BinaryWebSocketFrame( Unpooled.wrappedBuffer( StringUtil.encodeString( text ) ) ) + : new TextWebSocketFrame( text ) ); + } + + return null; + } + + case 2: // close + close(); + websocket.close(); + return null; + default: + return null; + } + } + + private void checkOpen() throws LuaException + { + if( closed ) throw new LuaException( "attempt to use a closed file" ); + } + + @Override + public void close() + { + closed = true; + + Channel channel = this.channel; + if( channel != null ) + { + channel.close(); + this.channel = null; + } + } +} diff --git a/remappedSrc/dan200/computercraft/core/apis/http/websocket/WebsocketHandler.java b/remappedSrc/dan200/computercraft/core/apis/http/websocket/WebsocketHandler.java new file mode 100644 index 000000000..0569cfffa --- /dev/null +++ b/remappedSrc/dan200/computercraft/core/apis/http/websocket/WebsocketHandler.java @@ -0,0 +1,125 @@ +/* + * 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.http.websocket; + +import dan200.computercraft.core.apis.http.HTTPRequestException; +import dan200.computercraft.core.apis.http.NetworkUtils; +import dan200.computercraft.core.tracking.TrackingField; +import io.netty.channel.ChannelHandlerContext; +import io.netty.channel.ConnectTimeoutException; +import io.netty.channel.SimpleChannelInboundHandler; +import io.netty.handler.codec.TooLongFrameException; +import io.netty.handler.codec.http.FullHttpResponse; +import io.netty.handler.codec.http.websocketx.*; +import io.netty.handler.timeout.ReadTimeoutException; +import io.netty.util.CharsetUtil; + +import static dan200.computercraft.core.apis.http.websocket.Websocket.MESSAGE_EVENT; + +public class WebsocketHandler extends SimpleChannelInboundHandler +{ + private final Websocket websocket; + private final WebSocketClientHandshaker handshaker; + + public WebsocketHandler( Websocket websocket, WebSocketClientHandshaker handshaker ) + { + this.handshaker = handshaker; + this.websocket = websocket; + } + + @Override + public void channelActive( ChannelHandlerContext ctx ) throws Exception + { + handshaker.handshake( ctx.channel() ); + super.channelActive( ctx ); + } + + @Override + public void channelInactive( ChannelHandlerContext ctx ) throws Exception + { + websocket.close( -1, "Websocket is inactive" ); + super.channelInactive( ctx ); + } + + @Override + public void channelRead0( ChannelHandlerContext ctx, Object msg ) + { + if( websocket.isClosed() ) return; + + if( !handshaker.isHandshakeComplete() ) + { + handshaker.finishHandshake( ctx.channel(), (FullHttpResponse) msg ); + websocket.success( ctx.channel() ); + return; + } + + if( msg instanceof FullHttpResponse ) + { + FullHttpResponse response = (FullHttpResponse) msg; + throw new IllegalStateException( "Unexpected FullHttpResponse (getStatus=" + response.status() + ", content=" + response.content().toString( CharsetUtil.UTF_8 ) + ')' ); + } + + WebSocketFrame frame = (WebSocketFrame) msg; + if( frame instanceof TextWebSocketFrame ) + { + String data = ((TextWebSocketFrame) frame).text(); + + websocket.environment().addTrackingChange( TrackingField.WEBSOCKET_INCOMING, data.length() ); + websocket.environment().queueEvent( MESSAGE_EVENT, new Object[] { websocket.address(), data, false } ); + } + else if( frame instanceof BinaryWebSocketFrame ) + { + byte[] converted = NetworkUtils.toBytes( frame.content() ); + + websocket.environment().addTrackingChange( TrackingField.WEBSOCKET_INCOMING, converted.length ); + websocket.environment().queueEvent( MESSAGE_EVENT, new Object[] { websocket.address(), converted, true } ); + } + else if( frame instanceof CloseWebSocketFrame ) + { + CloseWebSocketFrame closeFrame = (CloseWebSocketFrame) frame; + websocket.close( closeFrame.statusCode(), closeFrame.reasonText() ); + } + else if( frame instanceof PingWebSocketFrame ) + { + frame.content().retain(); + ctx.channel().writeAndFlush( new PongWebSocketFrame( frame.content() ) ); + } + } + + @Override + public void exceptionCaught( ChannelHandlerContext ctx, Throwable cause ) + { + ctx.close(); + + String message; + if( cause instanceof WebSocketHandshakeException || cause instanceof HTTPRequestException ) + { + message = cause.getMessage(); + } + else if( cause instanceof TooLongFrameException ) + { + message = "Message is too large"; + } + else if( cause instanceof ReadTimeoutException || cause instanceof ConnectTimeoutException ) + { + message = "Timed out"; + } + else + { + message = "Could not connect"; + } + + if( handshaker.isHandshakeComplete() ) + { + websocket.close( -1, message ); + } + else + { + websocket.failure( message ); + } + } +} diff --git a/remappedSrc/dan200/computercraft/core/computer/ApiWrapper.java b/remappedSrc/dan200/computercraft/core/computer/ApiWrapper.java new file mode 100644 index 000000000..02812ce72 --- /dev/null +++ b/remappedSrc/dan200/computercraft/core/computer/ApiWrapper.java @@ -0,0 +1,68 @@ +/* + * 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.computer; + +import dan200.computercraft.api.lua.ILuaAPI; +import dan200.computercraft.api.lua.ILuaContext; +import dan200.computercraft.api.lua.LuaException; + +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +/** + * A wrapper for {@link ILuaAPI}s which cleans up after a {@link ComputerSystem} when the computer is shutdown. + */ +final class ApiWrapper implements ILuaAPI +{ + private final ILuaAPI delegate; + private final ComputerSystem system; + + ApiWrapper( ILuaAPI delegate, ComputerSystem system ) + { + this.delegate = delegate; + this.system = system; + } + + @Override + public String[] getNames() + { + return delegate.getNames(); + } + + @Override + public void startup() + { + delegate.startup(); + } + + @Override + public void update() + { + delegate.update(); + } + + @Override + public void shutdown() + { + delegate.shutdown(); + system.unmountAll(); + } + + @Nonnull + @Override + public String[] getMethodNames() + { + return delegate.getMethodNames(); + } + + @Nullable + @Override + public Object[] callMethod( @Nonnull ILuaContext context, int method, @Nonnull Object[] arguments ) throws LuaException, InterruptedException + { + return delegate.callMethod( context, method, arguments ); + } +} diff --git a/remappedSrc/dan200/computercraft/core/computer/Computer.java b/remappedSrc/dan200/computercraft/core/computer/Computer.java new file mode 100644 index 000000000..5c54e325d --- /dev/null +++ b/remappedSrc/dan200/computercraft/core/computer/Computer.java @@ -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.computer; + +import com.google.common.base.Objects; +import dan200.computercraft.api.lua.ILuaAPI; +import dan200.computercraft.api.peripheral.IWorkMonitor; +import dan200.computercraft.core.apis.IAPIEnvironment; +import dan200.computercraft.core.filesystem.FileSystem; +import dan200.computercraft.core.terminal.Terminal; + +import java.util.concurrent.atomic.AtomicBoolean; + +/** + * Represents a computer which may exist in-world or elsewhere. + * + * Note, this class has several (read: far, far too many) responsibilities, so can get a little unwieldy at times. + * + *
    + *
  • Updates the {@link Environment}.
  • + *
  • Keeps track of whether the computer is on and blinking.
  • + *
  • Monitors whether the computer's visible state (redstone, on/off/blinking) has changed.
  • + *
  • Passes commands and events to the {@link ComputerExecutor}.
  • + *
  • Passes main thread tasks to the {@link MainThreadExecutor}.
  • + *
+ */ +public class Computer +{ + private static final int START_DELAY = 50; + + // Various properties of the computer + private int m_id; + private String m_label = null; + + // Read-only fields about the computer + private final IComputerEnvironment m_environment; + private final Terminal m_terminal; + private final ComputerExecutor executor; + private final MainThreadExecutor serverExecutor; + + // Additional state about the computer and its environment. + private boolean m_blinking = false; + private final Environment internalEnvironment = new Environment( this ); + private AtomicBoolean externalOutputChanged = new AtomicBoolean(); + + private boolean startRequested; + private int m_ticksSinceStart = -1; + + public Computer( IComputerEnvironment environment, Terminal terminal, int id ) + { + m_id = id; + m_environment = environment; + m_terminal = terminal; + + executor = new ComputerExecutor( this ); + serverExecutor = new MainThreadExecutor( this ); + } + + IComputerEnvironment getComputerEnvironment() + { + return m_environment; + } + + FileSystem getFileSystem() + { + return executor.getFileSystem(); + } + + Terminal getTerminal() + { + return m_terminal; + } + + public Environment getEnvironment() + { + return internalEnvironment; + } + + public IAPIEnvironment getAPIEnvironment() + { + return internalEnvironment; + } + + public boolean isOn() + { + return executor.isOn(); + } + + public void turnOn() + { + startRequested = true; + } + + public void shutdown() + { + executor.queueStop( false, false ); + } + + public void reboot() + { + executor.queueStop( true, false ); + } + + public void unload() + { + executor.queueStop( false, true ); + } + + public void queueEvent( String event, Object[] args ) + { + executor.queueEvent( event, args ); + } + + /** + * Queue a task to be run on the main thread, using {@link MainThread}. + * + * @param runnable The task to run + * @return If the task was successfully queued (namely, whether there is space on it). + */ + public boolean queueMainThread( Runnable runnable ) + { + return serverExecutor.enqueue( runnable ); + } + + public IWorkMonitor getMainThreadMonitor() + { + return serverExecutor; + } + + public int getID() + { + return m_id; + } + + public int assignID() + { + if( m_id < 0 ) + { + m_id = m_environment.assignNewID(); + } + return m_id; + } + + public void setID( int id ) + { + m_id = id; + } + + public String getLabel() + { + return m_label; + } + + public void setLabel( String label ) + { + if( !Objects.equal( label, m_label ) ) + { + m_label = label; + externalOutputChanged.set( true ); + } + } + + public void tick() + { + // We keep track of the number of ticks since the last start, only + if( m_ticksSinceStart >= 0 && m_ticksSinceStart <= START_DELAY ) m_ticksSinceStart++; + + if( startRequested && (m_ticksSinceStart < 0 || m_ticksSinceStart > START_DELAY) ) + { + startRequested = false; + if( !executor.isOn() ) + { + m_ticksSinceStart = 0; + executor.queueStart(); + } + } + + executor.tick(); + + // Update the environment's internal state. + internalEnvironment.update(); + + // Propagate the environment's output to the world. + if( internalEnvironment.updateOutput() ) externalOutputChanged.set( true ); + + // Set output changed if the terminal has changed from blinking to not + boolean blinking = m_terminal.getCursorBlink() && + m_terminal.getCursorX() >= 0 && m_terminal.getCursorX() < m_terminal.getWidth() && + m_terminal.getCursorY() >= 0 && m_terminal.getCursorY() < m_terminal.getHeight(); + if( blinking != m_blinking ) + { + m_blinking = blinking; + externalOutputChanged.set( true ); + } + } + + void markChanged() + { + externalOutputChanged.set( true ); + } + + public boolean pollAndResetChanged() + { + return externalOutputChanged.getAndSet( false ); + } + + public boolean isBlinking() + { + return isOn() && m_blinking; + } + + public void addApi( ILuaAPI api ) + { + executor.addApi( api ); + } +} diff --git a/remappedSrc/dan200/computercraft/core/computer/ComputerExecutor.java b/remappedSrc/dan200/computercraft/core/computer/ComputerExecutor.java new file mode 100644 index 000000000..78350d411 --- /dev/null +++ b/remappedSrc/dan200/computercraft/core/computer/ComputerExecutor.java @@ -0,0 +1,668 @@ +/* + * 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.computer; + +import dan200.computercraft.ComputerCraft; +import dan200.computercraft.api.filesystem.IMount; +import dan200.computercraft.api.filesystem.IWritableMount; +import dan200.computercraft.api.lua.ILuaAPI; +import dan200.computercraft.api.lua.ILuaAPIFactory; +import dan200.computercraft.core.apis.*; +import dan200.computercraft.core.filesystem.FileSystem; +import dan200.computercraft.core.filesystem.FileSystemException; +import dan200.computercraft.core.lua.CobaltLuaMachine; +import dan200.computercraft.core.lua.ILuaMachine; +import dan200.computercraft.core.lua.MachineResult; +import dan200.computercraft.core.terminal.Terminal; +import dan200.computercraft.core.tracking.Tracking; +import dan200.computercraft.shared.util.Colour; +import dan200.computercraft.shared.util.IoUtil; + +import javax.annotation.Nonnull; +import javax.annotation.Nullable; +import java.io.InputStream; +import java.util.ArrayDeque; +import java.util.ArrayList; +import java.util.List; +import java.util.Queue; +import java.util.concurrent.atomic.AtomicReference; +import java.util.concurrent.locks.ReentrantLock; + +/** + * The main task queue and executor for a single computer. This handles turning on and off a computer, as well as + * running events. + * + * When the computer is instructed to turn on or off, or handle an event, we queue a task and register this to be + * executed on the {@link ComputerThread}. Note, as we may be starting many events in a single tick, the external + * cannot lock on anything which may be held for a long time. + * + * The executor is effectively composed of two separate queues. Firstly, we have a "single element" queue + * {@link #command} which determines which state the computer should transition too. This is set by + * {@link #queueStart()} and {@link #queueStop(boolean, boolean)}. + * + * When a computer is on, we simply push any events onto to the {@link #eventQueue}. + * + * Both queues are run from the {@link #work()} method, which tries to execute a command if one exists, or resumes the + * machine with an event otherwise. + * + * One final responsibility for the executor is calling {@link ILuaAPI#update()} every tick, via the {@link #tick()} + * method. This should only be called when the computer is actually on ({@link #isOn}). + */ +final class ComputerExecutor +{ + private static final int QUEUE_LIMIT = 256; + + private static IMount romMount; + private static final Object romMountLock = new Object(); + + private final Computer computer; + private final List apis = new ArrayList<>(); + final TimeoutState timeout = new TimeoutState(); + + private FileSystem fileSystem; + + private ILuaMachine machine; + + /** + * Whether the computer is currently on. This is set to false when a shutdown starts, or when turning on completes + * (but just before the Lua machine is started). + * + * @see #isOnLock + */ + private volatile boolean isOn = false; + + /** + * The lock to acquire when you need to modify the "on state" of a computer. + * + * We hold this lock when running any command, and attempt to hold it when updating APIs. This ensures you don't + * update APIs while also starting/stopping them. + * + * @see #isOn + * @see #tick() + * @see #turnOn() + * @see #shutdown() + */ + private final ReentrantLock isOnLock = new ReentrantLock(); + + /** + * A lock used for any changes to {@link #eventQueue}, {@link #command} or {@link #onComputerQueue}. This will be + * used on the main thread, so locks should be kept as brief as possible. + */ + private final Object queueLock = new Object(); + + /** + * Determines if this executor is present within {@link ComputerThread}. + * + * @see #queueLock + * @see #enqueue() + * @see #afterWork() + */ + volatile boolean onComputerQueue = false; + + /** + * The amount of time this computer has used on a theoretical machine which shares work evenly amongst computers. + * + * @see ComputerThread + */ + long virtualRuntime = 0; + + /** + * The last time at which we updated {@link #virtualRuntime}. + * + * @see ComputerThread + */ + long vRuntimeStart; + + /** + * The command that {@link #work()} should execute on the computer thread. + * + * One sets the command with {@link #queueStart()} and {@link #queueStop(boolean, boolean)}. Neither of these will + * queue a new event if there is an existing one in the queue. + * + * Note, if command is not {@code null}, then some command is scheduled to be executed. Otherwise it is not + * currently in the queue (or is currently being executed). + */ + private volatile StateCommand command; + + /** + * The queue of events which should be executed when this computer is on. + * + * Note, this should be empty if this computer is off - it is cleared on shutdown and when turning on again. + */ + private final Queue eventQueue = new ArrayDeque<>( 4 ); + + /** + * Whether we interrupted an event and so should resume it instead of executing another task. + * + * @see #work() + * @see #resumeMachine(String, Object[]) + */ + private boolean interruptedEvent = false; + + /** + * Whether this executor has been closed, and will no longer accept any incoming commands or events. + * + * @see #queueStop(boolean, boolean) + */ + private boolean closed; + + private IWritableMount rootMount; + + /** + * The thread the executor is running on. This is non-null when performing work. We use this to ensure we're only + * doing one bit of work at one time. + * + * @see ComputerThread + */ + final AtomicReference executingThread = new AtomicReference<>(); + + ComputerExecutor( Computer computer ) + { + // Ensure the computer thread is running as required. + ComputerThread.start(); + + this.computer = computer; + + Environment environment = computer.getEnvironment(); + + // Add all default APIs to the loaded list. + apis.add( new TermAPI( environment ) ); + apis.add( new RedstoneAPI( environment ) ); + apis.add( new FSAPI( environment ) ); + apis.add( new PeripheralAPI( environment ) ); + apis.add( new OSAPI( environment ) ); + if( ComputerCraft.http_enable ) apis.add( new HTTPAPI( environment ) ); + + // Load in the externally registered APIs. + for( ILuaAPIFactory factory : ApiFactories.getAll() ) + { + ComputerSystem system = new ComputerSystem( environment ); + ILuaAPI api = factory.create( system ); + if( api != null ) apis.add( new ApiWrapper( api, system ) ); + } + } + + boolean isOn() + { + return isOn; + } + + FileSystem getFileSystem() + { + return fileSystem; + } + + Computer getComputer() + { + return computer; + } + + void addApi( ILuaAPI api ) + { + apis.add( api ); + } + + /** + * Schedule this computer to be started if not already on. + */ + void queueStart() + { + synchronized( queueLock ) + { + // We should only schedule a start if we're not currently on and there's turn on. + if( closed || isOn || command != null ) return; + + command = StateCommand.TURN_ON; + enqueue(); + } + } + + /** + * Schedule this computer to be stopped if not already on. + * + * @param reboot Reboot the computer after stopping + * @param close Close the computer after stopping. + * @see #closed + */ + void queueStop( boolean reboot, boolean close ) + { + synchronized( queueLock ) + { + if( closed ) return; + closed = close; + + StateCommand newCommand = reboot ? StateCommand.REBOOT : StateCommand.SHUTDOWN; + + // We should only schedule a stop if we're currently on and there's no shutdown pending. + if( !isOn || command != null ) + { + // If we're closing, set the command just in case. + if( close ) command = newCommand; + return; + } + + command = newCommand; + enqueue(); + } + } + + /** + * Abort this whole computer due to a timeout. This will immediately destroy the Lua machine, + * and then schedule a shutdown. + */ + void abort() + { + ILuaMachine machine = this.machine; + if( machine != null ) machine.close(); + + synchronized( queueLock ) + { + if( closed ) return; + command = StateCommand.ABORT; + if( isOn ) enqueue(); + } + } + + /** + * Queue an event if the computer is on + * + * @param event The event's name + * @param args The event's arguments + */ + void queueEvent( @Nonnull String event, @Nullable Object[] args ) + { + // Events should be skipped if we're not on. + if( !isOn ) return; + + synchronized( queueLock ) + { + // And if we've got some command in the pipeline, then don't queue events - they'll + // probably be disposed of anyway. + // We also limit the number of events which can be queued. + if( closed || command != null || eventQueue.size() >= QUEUE_LIMIT ) return; + + eventQueue.offer( new Event( event, args ) ); + enqueue(); + } + } + + /** + * Add this executor to the {@link ComputerThread} if not already there. + */ + private void enqueue() + { + synchronized( queueLock ) + { + if( !onComputerQueue ) ComputerThread.queue( this ); + } + } + + /** + * Update the internals of the executor. + */ + void tick() + { + if( isOn && isOnLock.tryLock() ) + { + // This horrific structure means we don't try to update APIs while the state is being changed + // (and so they may be running startup/shutdown). + // We use tryLock here, as it has minimal delay, and it doesn't matter if we miss an advance at the + // beginning or end of a computer's lifetime. + try + { + if( isOn ) + { + // Advance our APIs. + for( ILuaAPI api : apis ) api.update(); + } + } + finally + { + isOnLock.unlock(); + } + } + } + + private IMount getRomMount() + { + if( romMount != null ) return romMount; + + synchronized( romMountLock ) + { + if( romMount != null ) return romMount; + return romMount = computer.getComputerEnvironment().createResourceMount( "computercraft", "lua/rom" ); + } + } + + IWritableMount getRootMount() + { + if( rootMount == null ) + { + rootMount = computer.getComputerEnvironment().createSaveDirMount( + "computer/" + computer.assignID(), + computer.getComputerEnvironment().getComputerSpaceLimit() + ); + } + return rootMount; + } + + private FileSystem createFileSystem() + { + FileSystem filesystem = null; + try + { + filesystem = new FileSystem( "hdd", getRootMount() ); + + IMount romMount = getRomMount(); + if( romMount == null ) + { + displayFailure( "Cannot mount ROM", null ); + return null; + } + + filesystem.mount( "rom", "rom", romMount ); + return filesystem; + } + catch( FileSystemException e ) + { + if( filesystem != null ) filesystem.close(); + ComputerCraft.log.error( "Cannot mount computer filesystem", e ); + + displayFailure( "Cannot mount computer system", null ); + return null; + } + } + + private ILuaMachine createLuaMachine() + { + // Load the bios resource + InputStream biosStream = null; + try + { + biosStream = computer.getComputerEnvironment().createResourceFile( "computercraft", "lua/bios.lua" ); + } + catch( Exception ignored ) + { + } + + if( biosStream == null ) + { + displayFailure( "Error loading bios.lua", null ); + return null; + } + + // Create the lua machine + ILuaMachine machine = new CobaltLuaMachine( computer, timeout ); + + // Add the APIs + for( ILuaAPI api : apis ) machine.addAPI( api ); + + // Start the machine running the bios resource + MachineResult result = machine.loadBios( biosStream ); + IoUtil.closeQuietly( biosStream ); + + if( result.isError() ) + { + machine.close(); + displayFailure( "Error loading bios.lua", result.getMessage() ); + return null; + } + + return machine; + } + + private void turnOn() throws InterruptedException + { + isOnLock.lockInterruptibly(); + try + { + // Reset the terminal and event queue + computer.getTerminal().reset(); + interruptedEvent = false; + synchronized( queueLock ) + { + eventQueue.clear(); + } + + // Init filesystem + if( (fileSystem = createFileSystem()) == null ) + { + shutdown(); + return; + } + + // Init APIs + for( ILuaAPI api : apis ) api.startup(); + + // Init lua + if( (machine = createLuaMachine()) == null ) + { + shutdown(); + return; + } + + // Initialisation has finished, so let's mark ourselves as on. + isOn = true; + computer.markChanged(); + } + finally + { + isOnLock.unlock(); + } + + // Now actually start the computer, now that everything is set up. + resumeMachine( null, null ); + } + + private void shutdown() throws InterruptedException + { + isOnLock.lockInterruptibly(); + try + { + isOn = false; + interruptedEvent = false; + synchronized( queueLock ) + { + eventQueue.clear(); + } + + // Shutdown Lua machine + if( machine != null ) + { + machine.close(); + machine = null; + } + + // Shutdown our APIs + for( ILuaAPI api : apis ) api.shutdown(); + + // Unload filesystem + if( fileSystem != null ) + { + fileSystem.close(); + fileSystem = null; + } + + computer.getEnvironment().resetOutput(); + computer.markChanged(); + } + finally + { + isOnLock.unlock(); + } + } + + /** + * Called before calling {@link #work()}, setting up any important state. + */ + void beforeWork() + { + vRuntimeStart = System.nanoTime(); + timeout.startTimer(); + } + + /** + * Called after executing {@link #work()}. + * + * @return If we have more work to do. + */ + boolean afterWork() + { + if( interruptedEvent ) + { + timeout.pauseTimer(); + } + else + { + timeout.stopTimer(); + } + + Tracking.addTaskTiming( getComputer(), timeout.nanoCurrent() ); + + if( interruptedEvent ) return true; + + synchronized( queueLock ) + { + if( eventQueue.isEmpty() && command == null ) return onComputerQueue = false; + return true; + } + } + + /** + * The main worker function, called by {@link ComputerThread}. + * + * This either executes a {@link StateCommand} or attempts to run an event + * + * @throws InterruptedException If various locks could not be acquired. + * @see #command + * @see #eventQueue + */ + void work() throws InterruptedException + { + if( interruptedEvent ) + { + interruptedEvent = false; + if( machine != null ) + { + resumeMachine( null, null ); + return; + } + } + + StateCommand command; + Event event = null; + synchronized( queueLock ) + { + command = this.command; + this.command = null; + + // If we've no command, pull something from the event queue instead. + if( command == null ) + { + if( !isOn ) + { + // We're not on and had no command, but we had work queued. This should never happen, so clear + // the event queue just in case. + eventQueue.clear(); + return; + } + + event = eventQueue.poll(); + } + } + + if( command != null ) + { + switch( command ) + { + case TURN_ON: + if( isOn ) return; + turnOn(); + break; + + case SHUTDOWN: + + if( !isOn ) return; + computer.getTerminal().reset(); + shutdown(); + break; + + case REBOOT: + if( !isOn ) return; + computer.getTerminal().reset(); + shutdown(); + + computer.turnOn(); + break; + + case ABORT: + if( !isOn ) return; + displayFailure( "Error running computer", TimeoutState.ABORT_MESSAGE ); + shutdown(); + break; + } + } + else if( event != null ) + { + resumeMachine( event.name, event.args ); + } + } + + private void displayFailure( String message, String extra ) + { + Terminal terminal = computer.getTerminal(); + boolean colour = computer.getComputerEnvironment().isColour(); + terminal.reset(); + + // Display our primary error message + if( colour ) terminal.setTextColour( 15 - Colour.Red.ordinal() ); + terminal.write( message ); + + if( extra != null ) + { + // Display any additional information. This generally comes from the Lua Machine, such as compilation or + // runtime errors. + terminal.setCursorPos( 0, terminal.getCursorY() + 1 ); + terminal.write( extra ); + } + + // And display our generic "CC may be installed incorrectly" message. + terminal.setCursorPos( 0, terminal.getCursorY() + 1 ); + if( colour ) terminal.setTextColour( 15 - Colour.White.ordinal() ); + terminal.write( "ComputerCraft may be installed incorrectly" ); + } + + private void resumeMachine( String event, Object[] args ) throws InterruptedException + { + MachineResult result = machine.handleEvent( event, args ); + interruptedEvent = result.isPause(); + if( !result.isError() ) return; + + displayFailure( "Error running computer", result.getMessage() ); + shutdown(); + } + + private enum StateCommand + { + TURN_ON, + SHUTDOWN, + REBOOT, + ABORT, + } + + private static final class Event + { + final String name; + final Object[] args; + + private Event( String name, Object[] args ) + { + this.name = name; + this.args = args; + } + } +} diff --git a/remappedSrc/dan200/computercraft/core/computer/ComputerSide.java b/remappedSrc/dan200/computercraft/core/computer/ComputerSide.java new file mode 100644 index 000000000..d2937fd3b --- /dev/null +++ b/remappedSrc/dan200/computercraft/core/computer/ComputerSide.java @@ -0,0 +1,56 @@ +/* + * 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.computer; + +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +/** + * A side on a computer. Unlike {@link net.minecraft.util.EnumFacing}, this is relative to the direction the computer is + * facing.. + */ +public enum ComputerSide +{ + BOTTOM( "bottom" ), + TOP( "top" ), + BACK( "back" ), + FRONT( "front" ), + RIGHT( "right" ), + LEFT( "left" ); + + public static final String[] NAMES = new String[] { "bottom", "top", "back", "front", "right", "left" }; + + public static final int COUNT = 6; + + private static final ComputerSide[] VALUES = values(); + + private final String name; + + ComputerSide( String name ) {this.name = name;} + + @Nonnull + public static ComputerSide valueOf( int side ) + { + return VALUES[side]; + } + + @Nullable + public static ComputerSide valueOfInsensitive( @Nonnull String name ) + { + for( ComputerSide side : VALUES ) + { + if( side.name.equalsIgnoreCase( name ) ) return side; + } + + return null; + } + + public String getName() + { + return name; + } +} diff --git a/remappedSrc/dan200/computercraft/core/computer/ComputerSystem.java b/remappedSrc/dan200/computercraft/core/computer/ComputerSystem.java new file mode 100644 index 000000000..94eead340 --- /dev/null +++ b/remappedSrc/dan200/computercraft/core/computer/ComputerSystem.java @@ -0,0 +1,58 @@ +/* + * 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.computer; + +import dan200.computercraft.api.filesystem.IFileSystem; +import dan200.computercraft.api.lua.IComputerSystem; +import dan200.computercraft.api.lua.ILuaAPIFactory; +import dan200.computercraft.api.peripheral.IComputerAccess; +import dan200.computercraft.core.apis.ComputerAccess; +import dan200.computercraft.core.apis.IAPIEnvironment; +import dan200.computercraft.core.filesystem.FileSystem; + +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +/** + * Implementation of {@link IComputerAccess}/{@link IComputerSystem} for usage by externally registered APIs. + * + * @see dan200.computercraft.api.ComputerCraftAPI#registerAPIFactory(ILuaAPIFactory) + * @see ILuaAPIFactory + * @see ApiWrapper + */ +public class ComputerSystem extends ComputerAccess implements IComputerSystem +{ + private final IAPIEnvironment environment; + + ComputerSystem( IAPIEnvironment environment ) + { + super( environment ); + this.environment = environment; + } + + @Nonnull + @Override + public String getAttachmentName() + { + return "computer"; + } + + @Nullable + @Override + public IFileSystem getFileSystem() + { + FileSystem fs = environment.getFileSystem(); + return fs == null ? null : fs.getMountWrapper(); + } + + @Nullable + @Override + public String getLabel() + { + return environment.getLabel(); + } +} diff --git a/remappedSrc/dan200/computercraft/core/computer/ComputerThread.java b/remappedSrc/dan200/computercraft/core/computer/ComputerThread.java new file mode 100644 index 000000000..f1120e891 --- /dev/null +++ b/remappedSrc/dan200/computercraft/core/computer/ComputerThread.java @@ -0,0 +1,539 @@ +/* + * 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.computer; + +import dan200.computercraft.ComputerCraft; +import dan200.computercraft.shared.util.ThreadUtils; + +import javax.annotation.Nonnull; +import javax.annotation.Nullable; +import java.util.TreeSet; +import java.util.concurrent.ThreadFactory; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicReference; +import java.util.concurrent.locks.Condition; +import java.util.concurrent.locks.LockSupport; +import java.util.concurrent.locks.ReentrantLock; + +import static dan200.computercraft.core.computer.TimeoutState.ABORT_TIMEOUT; +import static dan200.computercraft.core.computer.TimeoutState.TIMEOUT; + +/** + * Responsible for running all tasks from a {@link Computer}. + * + * This is split into two components: the {@link TaskRunner}s, which pull an executor from the queue and execute it, and + * a single {@link Monitor} which observes all runners and kills them if they have not been terminated by + * {@link TimeoutState#isSoftAborted()}. + * + * Computers are executed using a priority system, with those who have spent less time executing having a higher + * priority than those hogging the thread. This, combined with {@link TimeoutState#isPaused()} means we can reduce the + * risk of badly behaved computers stalling execution for everyone else. + * + * This is done using an implementation of Linux's Completely Fair Scheduler. When a computer executes, we compute what + * share of execution time it has used (time executed/number of tasks). We then pick the computer who has the least + * "virtual execution time" (aka {@link ComputerExecutor#virtualRuntime}). + * + * When adding a computer to the queue, we make sure its "virtual runtime" is at least as big as the smallest runtime. + * This means that adding computers which have slept a lot do not then have massive priority over everyone else. See + * {@link #queue(ComputerExecutor)} for how this is implemented. + * + * In reality, it's unlikely that more than a few computers are waiting to execute at once, so this will not have much + * effect unless you have a computer hogging execution time. However, it is pretty effective in those situations. + * + * @see TimeoutState For how hard timeouts are handled. + * @see ComputerExecutor For how computers actually do execution. + */ +public final class ComputerThread +{ + /** + * How often the computer thread monitor should run, in milliseconds + * + * @see Monitor + */ + private static final int MONITOR_WAKEUP = 100; + + /** + * The target latency between executing two tasks on a single machine. + * + * An average tick takes 50ms, and so we ideally need to have handled a couple of events within that window in order + * to have a perceived low latency. + */ + private static final long DEFAULT_LATENCY = TimeUnit.MILLISECONDS.toNanos( 50 ); + + /** + * The minimum value that {@link #DEFAULT_LATENCY} can have when scaled. + * + * From statistics gathered on SwitchCraft, almost all machines will execute under 15ms, 75% under 1.5ms, with the + * mean being about 3ms. Most computers shouldn't be too impacted with having such a short period to execute in. + */ + private static final long DEFAULT_MIN_PERIOD = TimeUnit.MILLISECONDS.toNanos( 5 ); + + /** + * The maximum number of tasks before we have to start scaling latency linearly. + */ + private static final long LATENCY_MAX_TASKS = DEFAULT_LATENCY / DEFAULT_MIN_PERIOD; + + /** + * Lock used for modifications to the array of current threads. + */ + private static final Object threadLock = new Object(); + + /** + * Whether the computer thread system is currently running + */ + private static volatile boolean running = false; + + /** + * The current task manager. + */ + private static Thread monitor; + + /** + * The array of current runners, and their owning threads. + */ + private static TaskRunner[] runners; + + private static long latency; + private static long minPeriod; + + private static final ReentrantLock computerLock = new ReentrantLock(); + + private static final Condition hasWork = computerLock.newCondition(); + + /** + * Active queues to execute + */ + private static final TreeSet computerQueue = new TreeSet<>( ( a, b ) -> { + if( a == b ) return 0; // Should never happen, but let's be consistent here + + long at = a.virtualRuntime, bt = b.virtualRuntime; + if( at == bt ) return Integer.compare( a.hashCode(), b.hashCode() ); + return at < bt ? -1 : 1; + } ); + + /** + * The minimum {@link ComputerExecutor#virtualRuntime} time on the tree. + */ + private static long minimumVirtualRuntime = 0; + + private static final ThreadFactory monitorFactory = ThreadUtils.factory( "Computer-Monitor" ); + private static final ThreadFactory runnerFactory = ThreadUtils.factory( "Computer-Runner" ); + + private ComputerThread() {} + + /** + * Start the computer thread + */ + static void start() + { + synchronized( threadLock ) + { + running = true; + + if( runners == null ) + { + // TODO: Change the runners length on config reloads + runners = new TaskRunner[ComputerCraft.computer_threads]; + + // latency and minPeriod are scaled by 1 + floor(log2(threads)). We can afford to execute tasks for + // longer when executing on more than one thread. + long factor = 64 - Long.numberOfLeadingZeros( runners.length ); + latency = DEFAULT_LATENCY * factor; + minPeriod = DEFAULT_MIN_PERIOD * factor; + } + + for( int i = 0; i < runners.length; i++ ) + { + TaskRunner runner = runners[i]; + if( runner == null || runner.owner == null || !runner.owner.isAlive() ) + { + // Mark the old runner as dead, just in case. + if( runner != null ) runner.running = false; + // And start a new runner + runnerFactory.newThread( runners[i] = new TaskRunner() ).start(); + } + } + + if( monitor == null || !monitor.isAlive() ) (monitor = monitorFactory.newThread( new Monitor() )).start(); + } + } + + /** + * Attempt to stop the computer thread. This interrupts each runner, and clears the task queue. + */ + public static void stop() + { + synchronized( threadLock ) + { + running = false; + if( runners != null ) + { + for( TaskRunner runner : runners ) + { + if( runner == null ) continue; + + runner.running = false; + if( runner.owner != null ) runner.owner.interrupt(); + } + } + } + + computerLock.lock(); + try + { + computerQueue.clear(); + } + finally + { + computerLock.unlock(); + } + } + + /** + * Mark a computer as having work, enqueuing it on the thread + * + * You must be holding {@link ComputerExecutor}'s {@code queueLock} when calling this method - it should only + * be called from {@code enqueue}. + * + * @param executor The computer to execute work on. + */ + static void queue( @Nonnull ComputerExecutor executor ) + { + computerLock.lock(); + try + { + if( executor.onComputerQueue ) throw new IllegalStateException( "Cannot queue already queued executor" ); + executor.onComputerQueue = true; + + updateRuntimes( null ); + + // We're not currently on the queue, so update its current execution time to + // ensure its at least as high as the minimum. + long newRuntime = minimumVirtualRuntime; + + if( executor.virtualRuntime == 0 ) + { + // Slow down new computers a little bit. + newRuntime += scaledPeriod(); + } + else + { + // Give a small boost to computers which have slept a little. + newRuntime -= latency / 2; + } + + executor.virtualRuntime = Math.max( newRuntime, executor.virtualRuntime ); + + // Add to the queue, and signal the workers. + computerQueue.add( executor ); + hasWork.signal(); + } + finally + { + computerLock.unlock(); + } + } + + + /** + * Update the {@link ComputerExecutor#virtualRuntime}s of all running tasks, and then update the + * {@link #minimumVirtualRuntime} based on the current tasks. + * + * This is called before queueing tasks, to ensure that {@link #minimumVirtualRuntime} is up-to-date. + */ + private static void updateRuntimes( @Nullable ComputerExecutor current ) + { + long minRuntime = Long.MAX_VALUE; + + // If we've a task on the queue, use that as our base time. + if( !computerQueue.isEmpty() ) minRuntime = computerQueue.first().virtualRuntime; + + // Update all the currently executing tasks + long now = System.nanoTime(); + int tasks = 1 + computerQueue.size(); + TaskRunner[] currentRunners = runners; + if( currentRunners != null ) + { + for( TaskRunner runner : currentRunners ) + { + if( runner == null ) continue; + ComputerExecutor executor = runner.currentExecutor.get(); + if( executor == null ) continue; + + // We do two things here: first we update the task's virtual runtime based on when we + // last checked, and then we check the minimum. + minRuntime = Math.min( minRuntime, executor.virtualRuntime += (now - executor.vRuntimeStart) / tasks ); + executor.vRuntimeStart = now; + } + } + + // And update the most recently executed one (if set). + if( current != null ) + { + minRuntime = Math.min( minRuntime, current.virtualRuntime += (now - current.vRuntimeStart) / tasks ); + } + + if( minRuntime > minimumVirtualRuntime && minRuntime < Long.MAX_VALUE ) + { + minimumVirtualRuntime = minRuntime; + } + } + + /** + * Ensure the "currently working" state of the executor is reset, the timings are updated, and then requeue the + * executor if needed. + * + * @param runner The runner this task was on. + * @param executor The executor to requeue + */ + private static void afterWork( TaskRunner runner, ComputerExecutor executor ) + { + // Clear the executor's thread. + Thread currentThread = executor.executingThread.getAndSet( null ); + if( currentThread != runner.owner ) + { + ComputerCraft.log.error( + "Expected computer #{} to be running on {}, but already running on {}. This is a SERIOUS bug, please report with your debug.log.", + executor.getComputer().getID(), runner.owner.getName(), currentThread == null ? "nothing" : currentThread.getName() + ); + } + + computerLock.lock(); + try + { + updateRuntimes( executor ); + + // If we've no more tasks, just return. + if( !executor.afterWork() ) return; + + // Otherwise, add to the queue, and signal any waiting workers. + computerQueue.add( executor ); + hasWork.signal(); + } + finally + { + computerLock.unlock(); + } + } + + /** + * The scaled period for a single task + * + * @return The scaled period for the task + * @see #DEFAULT_LATENCY + * @see #DEFAULT_MIN_PERIOD + * @see #LATENCY_MAX_TASKS + */ + static long scaledPeriod() + { + // +1 to include the current task + int count = 1 + computerQueue.size(); + return count < LATENCY_MAX_TASKS ? latency / count : minPeriod; + } + + /** + * Determine if the thread has computers queued up + * + * @return If we have work queued up. + */ + static boolean hasPendingWork() + { + return !computerQueue.isEmpty(); + } + + /** + * Observes all currently active {@link TaskRunner}s and terminates their tasks once they have exceeded the hard + * abort limit. + * + * @see TimeoutState + */ + private static final class Monitor implements Runnable + { + @Override + public void run() + { + try + { + while( true ) + { + Thread.sleep( MONITOR_WAKEUP ); + + TaskRunner[] currentRunners = ComputerThread.runners; + if( currentRunners != null ) + { + for( int i = 0; i < currentRunners.length; i++ ) + { + TaskRunner runner = currentRunners[i]; + // If we've no runner, skip. + if( runner == null || runner.owner == null || !runner.owner.isAlive() ) + { + if( !running ) continue; + + // Mark the old runner as dead and start a new one. + ComputerCraft.log.warn( "Previous runner ({}) has crashed, restarting!", + runner != null && runner.owner != null ? runner.owner.getName() : runner ); + if( runner != null ) runner.running = false; + runnerFactory.newThread( runners[i] = new TaskRunner() ).start(); + } + + // If the runner has no work, skip + ComputerExecutor executor = runner.currentExecutor.get(); + if( executor == null ) continue; + + // If we're still within normal execution times (TIMEOUT) or soft abort (ABORT_TIMEOUT), + // then we can let the Lua machine do its work. + long afterStart = executor.timeout.nanoCumulative(); + long afterHardAbort = afterStart - TIMEOUT - ABORT_TIMEOUT; + if( afterHardAbort < 0 ) continue; + + // Set the hard abort flag. + executor.timeout.hardAbort(); + executor.abort(); + + if( afterHardAbort >= ABORT_TIMEOUT ) + { + // If we've hard aborted but we're still not dead, dump the stack trace and interrupt + // the task. + timeoutTask( executor, runner.owner, afterStart ); + runner.owner.interrupt(); + } + else if( afterHardAbort >= ABORT_TIMEOUT * 2 ) + { + // If we've hard aborted and interrupted, and we're still not dead, then mark the runner + // as dead, finish off the task, and spawn a new runner. + timeoutTask( executor, runner.owner, afterStart ); + runner.running = false; + runner.owner.interrupt(); + + ComputerExecutor thisExecutor = runner.currentExecutor.getAndSet( null ); + if( thisExecutor != null ) afterWork( runner, executor ); + + synchronized( threadLock ) + { + if( running && runners.length > i && runners[i] == runner ) + { + runnerFactory.newThread( currentRunners[i] = new TaskRunner() ).start(); + } + } + } + } + } + } + } + catch( InterruptedException ignored ) + { + } + } + } + + /** + * Pulls tasks from the {@link #computerQueue} queue and runs them. + * + * This is responsible for running the {@link ComputerExecutor#work()}, {@link ComputerExecutor#beforeWork()} and + * {@link ComputerExecutor#afterWork()} functions. Everything else is either handled by the executor, timeout + * state or monitor. + */ + private static final class TaskRunner implements Runnable + { + Thread owner; + volatile boolean running = true; + + final AtomicReference currentExecutor = new AtomicReference<>(); + + @Override + public void run() + { + owner = Thread.currentThread(); + + tasks: + while( running && ComputerThread.running ) + { + // Wait for an active queue to execute + ComputerExecutor executor; + try + { + computerLock.lockInterruptibly(); + try + { + while( computerQueue.isEmpty() ) hasWork.await(); + executor = computerQueue.pollFirst(); + assert executor != null : "hasWork should ensure we never receive null work"; + } + finally + { + computerLock.unlock(); + } + } + catch( InterruptedException ignored ) + { + // If we've been interrupted, our running flag has probably been reset, so we'll + // just jump into the next iteration. + continue; + } + + // If we're trying to executing some task on this computer while someone else is doing work, something + // is seriously wrong. + while( !executor.executingThread.compareAndSet( null, owner ) ) + { + Thread existing = executor.executingThread.get(); + if( existing != null ) + { + ComputerCraft.log.error( + "Trying to run computer #{} on thread {}, but already running on {}. This is a SERIOUS bug, please report with your debug.log.", + executor.getComputer().getID(), owner.getName(), existing.getName() + ); + continue tasks; + } + } + + // Reset the timers + executor.beforeWork(); + + // And then set the current executor. It's important to do it afterwards, as otherwise we introduce + // race conditions with the monitor. + currentExecutor.set( executor ); + + // Execute the task + try + { + executor.work(); + } + catch( Exception | LinkageError | VirtualMachineError e ) + { + ComputerCraft.log.error( "Error running task on computer #" + executor.getComputer().getID(), e ); + } + finally + { + ComputerExecutor thisExecutor = currentExecutor.getAndSet( null ); + if( thisExecutor != null ) afterWork( this, executor ); + } + } + } + } + + private static void timeoutTask( ComputerExecutor executor, Thread thread, long time ) + { + if( !ComputerCraft.logPeripheralErrors ) return; + + StringBuilder builder = new StringBuilder() + .append( "Terminating computer #" ).append( executor.getComputer().getID() ) + .append( " due to timeout (running for " ).append( time * 1e-9 ) + .append( " seconds). This is NOT a bug, but may mean a computer is misbehaving. " ) + .append( thread.getName() ) + .append( " is currently " ) + .append( thread.getState() ); + Object blocking = LockSupport.getBlocker( thread ); + if( blocking != null ) builder.append( "\n on " ).append( blocking ); + + for( StackTraceElement element : thread.getStackTrace() ) + { + builder.append( "\n at " ).append( element ); + } + + ComputerCraft.log.warn( builder.toString() ); + } +} diff --git a/remappedSrc/dan200/computercraft/core/computer/Environment.java b/remappedSrc/dan200/computercraft/core/computer/Environment.java new file mode 100644 index 000000000..b23ad377e --- /dev/null +++ b/remappedSrc/dan200/computercraft/core/computer/Environment.java @@ -0,0 +1,312 @@ +/* + * 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.computer; + +import dan200.computercraft.api.peripheral.IPeripheral; +import dan200.computercraft.api.peripheral.IWorkMonitor; +import dan200.computercraft.core.apis.IAPIEnvironment; +import dan200.computercraft.core.filesystem.FileSystem; +import dan200.computercraft.core.terminal.Terminal; +import dan200.computercraft.core.tracking.Tracking; +import dan200.computercraft.core.tracking.TrackingField; + +import javax.annotation.Nonnull; +import java.util.Arrays; + +/** + * Represents the "environment" that a {@link Computer} exists in. + * + * This handles storing and updating of peripherals and redstone. + * + *

Redstone

+ * We holds three kinds of arrays for redstone, in normal and bundled versions: + *
    + *
  • {@link #internalOutput} is the redstone output which the computer has currently set. This is read on both + * threads, and written on the computer thread.
  • + *
  • {@link #externalOutput} is the redstone output currently propagated to the world. This is only read and written + * on the main thread.
  • + *
  • {@link #input} is the redstone input from external sources. This is read on both threads, and written on the main + * thread.
  • + *
+ * + *

Peripheral

+ * We also keep track of peripherals. These are read on both threads, and only written on the main thread. + */ +public final class Environment implements IAPIEnvironment +{ + private final Computer computer; + + private boolean internalOutputChanged = false; + private final int[] internalOutput = new int[ComputerSide.COUNT]; + private final int[] internalBundledOutput = new int[ComputerSide.COUNT]; + + private final int[] externalOutput = new int[ComputerSide.COUNT]; + private final int[] externalBundledOutput = new int[ComputerSide.COUNT]; + + private boolean inputChanged = false; + private final int[] input = new int[ComputerSide.COUNT]; + private final int[] bundledInput = new int[ComputerSide.COUNT]; + + private final IPeripheral[] peripherals = new IPeripheral[ComputerSide.COUNT]; + private IPeripheralChangeListener peripheralListener = null; + + Environment( Computer computer ) + { + this.computer = computer; + } + + @Override + public int getComputerID() + { + return computer.assignID(); + } + + @Nonnull + @Override + public IComputerEnvironment getComputerEnvironment() + { + return computer.getComputerEnvironment(); + } + + @Nonnull + @Override + public IWorkMonitor getMainThreadMonitor() + { + return computer.getMainThreadMonitor(); + } + + @Nonnull + @Override + public Terminal getTerminal() + { + return computer.getTerminal(); + } + + @Override + public FileSystem getFileSystem() + { + return computer.getFileSystem(); + } + + @Override + public void shutdown() + { + computer.shutdown(); + } + + @Override + public void reboot() + { + computer.reboot(); + } + + @Override + public void queueEvent( String event, Object[] args ) + { + computer.queueEvent( event, args ); + } + + @Override + public int getInput( ComputerSide side ) + { + return input[side.ordinal()]; + } + + @Override + public int getBundledInput( ComputerSide side ) + { + return bundledInput[side.ordinal()]; + } + + @Override + public void setOutput( ComputerSide side, int output ) + { + int index = side.ordinal(); + synchronized( internalOutput ) + { + if( internalOutput[index] != output ) + { + internalOutput[index] = output; + internalOutputChanged = true; + } + } + } + + @Override + public int getOutput( ComputerSide side ) + { + synchronized( internalOutput ) + { + return computer.isOn() ? internalOutput[side.ordinal()] : 0; + } + } + + @Override + public void setBundledOutput( ComputerSide side, int output ) + { + int index = side.ordinal(); + synchronized( internalOutput ) + { + if( internalBundledOutput[index] != output ) + { + internalBundledOutput[index] = output; + internalOutputChanged = true; + } + } + } + + @Override + public int getBundledOutput( ComputerSide side ) + { + synchronized( internalOutput ) + { + return computer.isOn() ? internalBundledOutput[side.ordinal()] : 0; + } + } + + public int getExternalRedstoneOutput( ComputerSide side ) + { + return computer.isOn() ? externalOutput[side.ordinal()] : 0; + } + + public int getExternalBundledRedstoneOutput( ComputerSide side ) + { + return computer.isOn() ? externalBundledOutput[side.ordinal()] : 0; + } + + public void setRedstoneInput( ComputerSide side, int level ) + { + int index = side.ordinal(); + if( input[index] != level ) + { + input[index] = level; + inputChanged = true; + } + } + + public void setBundledRedstoneInput( ComputerSide side, int combination ) + { + int index = side.ordinal(); + if( bundledInput[index] != combination ) + { + bundledInput[index] = combination; + inputChanged = true; + } + } + + /** + * Called on the main thread to update the internal state of the computer. + * + * This just queues a {@code redstone} event if the input has changed. + */ + void update() + { + if( inputChanged ) + { + inputChanged = false; + queueEvent( "redstone", null ); + } + } + + /** + * Called on the main thread to propagate the internal outputs to the external ones. + * + * @return If the outputs have changed. + */ + boolean updateOutput() + { + // Mark output as changed if the internal redstone has changed + synchronized( internalOutput ) + { + if( !internalOutputChanged ) return false; + + boolean changed = false; + + for( int i = 0; i < ComputerSide.COUNT; i++ ) + { + if( externalOutput[i] != internalOutput[i] ) + { + externalOutput[i] = internalOutput[i]; + changed = true; + } + + if( externalBundledOutput[i] != internalBundledOutput[i] ) + { + externalBundledOutput[i] = internalBundledOutput[i]; + changed = true; + } + } + + internalOutputChanged = false; + + return changed; + } + } + + void resetOutput() + { + // Reset redstone output + synchronized( internalOutput ) + { + Arrays.fill( internalOutput, 0 ); + Arrays.fill( internalBundledOutput, 0 ); + internalOutputChanged = true; + } + } + + @Override + public IPeripheral getPeripheral( ComputerSide side ) + { + synchronized( peripherals ) + { + return peripherals[side.ordinal()]; + } + } + + public void setPeripheral( ComputerSide side, IPeripheral peripheral ) + { + synchronized( peripherals ) + { + int index = side.ordinal(); + IPeripheral existing = peripherals[index]; + if( (existing == null && peripheral != null) || + (existing != null && peripheral == null) || + (existing != null && !existing.equals( peripheral )) ) + { + peripherals[index] = peripheral; + if( peripheralListener != null ) peripheralListener.onPeripheralChanged( side, peripheral ); + } + } + } + + @Override + public void setPeripheralChangeListener( IPeripheralChangeListener listener ) + { + synchronized( peripherals ) + { + peripheralListener = listener; + } + } + + @Override + public String getLabel() + { + return computer.getLabel(); + } + + @Override + public void setLabel( String label ) + { + computer.setLabel( label ); + } + + @Override + public void addTrackingChange( @Nonnull TrackingField field, long change ) + { + Tracking.addValue( computer, field, change ); + } +} diff --git a/remappedSrc/dan200/computercraft/core/computer/IComputerEnvironment.java b/remappedSrc/dan200/computercraft/core/computer/IComputerEnvironment.java new file mode 100644 index 000000000..7c9797585 --- /dev/null +++ b/remappedSrc/dan200/computercraft/core/computer/IComputerEnvironment.java @@ -0,0 +1,33 @@ +/* + * 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.computer; + +import dan200.computercraft.api.filesystem.IMount; +import dan200.computercraft.api.filesystem.IWritableMount; + +import java.io.InputStream; + +public interface IComputerEnvironment +{ + int getDay(); + + double getTimeOfDay(); + + boolean isColour(); + + long getComputerSpaceLimit(); + + String getHostString(); + + int assignNewID(); + + IWritableMount createSaveDirMount( String subPath, long capacity ); + + IMount createResourceMount( String domain, String subPath ); + + InputStream createResourceFile( String domain, String subPath ); +} diff --git a/remappedSrc/dan200/computercraft/core/computer/MainThread.java b/remappedSrc/dan200/computercraft/core/computer/MainThread.java new file mode 100644 index 000000000..9bddaee4e --- /dev/null +++ b/remappedSrc/dan200/computercraft/core/computer/MainThread.java @@ -0,0 +1,195 @@ +/* + * 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.computer; + +import dan200.computercraft.ComputerCraft; +import dan200.computercraft.api.lua.ILuaTask; + +import javax.annotation.Nonnull; +import java.util.HashSet; +import java.util.TreeSet; +import java.util.concurrent.atomic.AtomicLong; + +/** + * Runs tasks on the main (server) thread, ticks {@link MainThreadExecutor}s, and limits how much time is used this + * tick. + * + * Similar to {@link MainThreadExecutor}, the {@link MainThread} can be in one of three states: cool, hot and cooling. + * However, the implementation here is a little different: + * + * {@link MainThread} starts cool, and runs as many tasks as it can in the current {@link #budget}ns. Any external tasks + * (those run by tile entities, etc...) will also consume the budget + * + * Next tick, we put {@link ComputerCraft#maxMainGlobalTime} into our budget (and clamp it to that value to). If we're + * still over budget, then we should not execute any work (either as part of {@link MainThread} or externally). + */ +public final class MainThread +{ + /** + * An internal counter for {@link ILuaTask} ids. + * + * @see dan200.computercraft.api.lua.ILuaContext#issueMainThreadTask(ILuaTask) + * @see #getUniqueTaskID() + */ + private static final AtomicLong lastTaskId = new AtomicLong(); + + /** + * The queue of {@link MainThreadExecutor}s with tasks to perform. + */ + private static final TreeSet executors = new TreeSet<>( ( a, b ) -> { + if( a == b ) return 0; // Should never happen, but let's be consistent here + + long at = a.virtualTime, bt = b.virtualTime; + if( at == bt ) return Integer.compare( a.hashCode(), b.hashCode() ); + return at < bt ? -1 : 1; + } ); + + /** + * The set of executors which went over budget in a previous tick, and are waiting for their time to run down. + * + * @see MainThreadExecutor#tickCooling() + * @see #cooling(MainThreadExecutor) + */ + private static final HashSet cooling = new HashSet<>(); + + /** + * The current tick number. This is used by {@link MainThreadExecutor} to determine when to reset its own time + * counter. + * + * @see #currentTick() + */ + private static int currentTick; + + /** + * The remaining budgeted time for this tick. This may be negative, in the case that we've gone over budget. + */ + private static long budget; + + /** + * Whether we should be executing any work this tick. + * + * This is true iff {@code MAX_TICK_TIME - currentTime} was true at the beginning of the tick. + */ + private static boolean canExecute = true; + + private static long minimumTime = 0; + + private MainThread() {} + + public static long getUniqueTaskID() + { + return lastTaskId.incrementAndGet(); + } + + static void queue( @Nonnull MainThreadExecutor executor, boolean sleeper ) + { + synchronized( executors ) + { + if( executor.onQueue ) throw new IllegalStateException( "Cannot queue already queued executor" ); + executor.onQueue = true; + executor.updateTime(); + + // We're not currently on the queue, so update its current execution time to + // ensure its at least as high as the minimum. + long newRuntime = minimumTime; + + // Slow down new computers a little bit. + if( executor.virtualTime == 0 ) newRuntime += ComputerCraft.maxMainComputerTime; + + executor.virtualTime = Math.max( newRuntime, executor.virtualTime ); + + executors.add( executor ); + } + } + + static void cooling( @Nonnull MainThreadExecutor executor ) + { + cooling.add( executor ); + } + + static void consumeTime( long time ) + { + budget -= time; + } + + static boolean canExecute() + { + return canExecute; + } + + static int currentTick() + { + return currentTick; + } + + public static void executePendingTasks() + { + // Move onto the next tick and cool down the global executor. We're allowed to execute if we have _any_ time + // allocated for this tick. This means we'll stick much closer to doing MAX_TICK_TIME work every tick. + // + // Of course, we'll go over the MAX_TICK_TIME most of the time, but eventually that overrun will accumulate + // and we'll skip a whole tick - bringing the average back down again. + currentTick++; + budget = Math.min( budget + ComputerCraft.maxMainGlobalTime, ComputerCraft.maxMainGlobalTime ); + canExecute = budget > 0; + + // Cool down any warm computers. + cooling.removeIf( MainThreadExecutor::tickCooling ); + + if( !canExecute ) return; + + // Run until we meet the deadline. + long start = System.nanoTime(); + long deadline = start + budget; + while( true ) + { + MainThreadExecutor executor; + synchronized( executors ) + { + executor = executors.pollFirst(); + } + if( executor == null ) break; + + long taskStart = System.nanoTime(); + executor.execute(); + + long taskStop = System.nanoTime(); + synchronized( executors ) + { + if( executor.afterExecute( taskStop - taskStart ) ) executors.add( executor ); + + // Compute the new minimum time (including the next task on the queue too). Note that this may also include + // time spent in external tasks. + long newMinimum = executor.virtualTime; + if( !executors.isEmpty() ) + { + MainThreadExecutor next = executors.first(); + if( next.virtualTime < newMinimum ) newMinimum = next.virtualTime; + } + minimumTime = Math.max( minimumTime, newMinimum ); + } + + if( taskStop >= deadline ) break; + } + + consumeTime( System.nanoTime() - start ); + } + + public static void reset() + { + currentTick = 0; + budget = 0; + canExecute = true; + minimumTime = 0; + lastTaskId.set( 0 ); + cooling.clear(); + synchronized( executors ) + { + executors.clear(); + } + } +} diff --git a/remappedSrc/dan200/computercraft/core/computer/MainThreadExecutor.java b/remappedSrc/dan200/computercraft/core/computer/MainThreadExecutor.java new file mode 100644 index 000000000..86ede82d3 --- /dev/null +++ b/remappedSrc/dan200/computercraft/core/computer/MainThreadExecutor.java @@ -0,0 +1,250 @@ +/* + * 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.computer; + +import dan200.computercraft.ComputerCraft; +import dan200.computercraft.api.peripheral.IWorkMonitor; +import dan200.computercraft.core.tracking.Tracking; +import dan200.computercraft.shared.turtle.core.TurtleBrain; +import net.minecraft.block.entity.BlockEntity; + +import javax.annotation.Nonnull; +import java.util.ArrayDeque; +import java.util.Queue; +import java.util.concurrent.TimeUnit; + +/** + * Keeps track of tasks that a {@link Computer} should run on the main thread and how long that has been spent executing + * them. + * + * This provides rate-limiting mechanism for tasks enqueued with {@link Computer#queueMainThread(Runnable)}, but also + * those run elsewhere (such as during the turtle's tick - see {@link TurtleBrain#update()}). In order to handle this, + * the executor goes through three stages: + * + * When {@link State#COOL}, the computer is allocated {@link ComputerCraft#maxMainComputerTime}ns to execute any work + * this tick. At the beginning of the tick, we execute as many {@link MainThread} tasks as possible, until our + * time-frame or the global time frame has expired. + * + * Then, when other objects (such as {@link BlockEntity}) are ticked, we update how much time we've used using + * {@link IWorkMonitor#trackWork(long, TimeUnit)}. + * + * Now, if anywhere during this period, we use more than our allocated time slice, the executor is marked as + * {@link State#HOT}. This means it will no longer be able to execute {@link MainThread} tasks (though will still + * execute tile entity tasks, in order to prevent the main thread from exhausting work every tick). + * + * At the beginning of the next tick, we increment the budget e by {@link ComputerCraft#maxMainComputerTime} and any + * {@link State#HOT} executors are marked as {@link State#COOLING}. They will remain cooling until their budget is + * fully replenished (is equal to {@link ComputerCraft#maxMainComputerTime}). Note, this is different to + * {@link MainThread}, which allows running when it has any budget left. When cooling, no tasks are executed - + * be they on the tile entity or main thread. + * + * This mechanism means that, on average, computers will use at most {@link ComputerCraft#maxMainComputerTime}ns per + * second, but one task source will not prevent others from executing. + * + * @see MainThread + * @see IWorkMonitor + * @see Computer#getMainThreadMonitor() + * @see Computer#queueMainThread(Runnable) + */ +final class MainThreadExecutor implements IWorkMonitor +{ + /** + * The maximum number of {@link MainThread} tasks allowed on the queue. + */ + private static final int MAX_TASKS = 5000; + + private final Computer computer; + + /** + * A lock used for any changes to {@link #tasks}, or {@link #onQueue}. This will be + * used on the main thread, so locks should be kept as brief as possible. + */ + private final Object queueLock = new Object(); + + /** + * The queue of tasks which should be executed. + * + * @see #queueLock + */ + private final Queue tasks = new ArrayDeque<>( 4 ); + + /** + * Determines if this executor is currently present on the queue. + * + * This should be true iff {@link #tasks} is non-empty. + * + * @see #queueLock + * @see #enqueue(Runnable) + * @see #afterExecute(long) + */ + volatile boolean onQueue; + + /** + * The remaining budgeted time for this tick. This may be negative, in the case that we've gone over budget. + * + * @see #tickCooling() + * @see #consumeTime(long) + */ + private long budget = 0; + + /** + * The last tick that {@link #budget} was updated. + * + * @see #tickCooling() + * @see #consumeTime(long) + */ + private int currentTick = -1; + + /** + * The current state of this executor. + * + * @see #canWork() + */ + private State state = State.COOL; + + private long pendingTime; + + long virtualTime; + + MainThreadExecutor( Computer computer ) + { + this.computer = computer; + } + + /** + * Push a task onto this executor's queue, pushing it onto the {@link MainThread} if needed. + * + * @param runnable The task to run on the main thread. + * @return Whether this task was enqueued (namely, was there space). + */ + boolean enqueue( Runnable runnable ) + { + synchronized( queueLock ) + { + if( tasks.size() >= MAX_TASKS || !tasks.offer( runnable ) ) return false; + if( !onQueue && state == State.COOL ) MainThread.queue( this, true ); + return true; + } + } + + void execute() + { + if( state != State.COOL ) return; + + Runnable task; + synchronized( queueLock ) + { + task = tasks.poll(); + } + + if( task != null ) task.run(); + } + + /** + * Update the time taken to run an {@link #enqueue(Runnable)} task. + * + * @param time The time some task took to run. + * @return Whether this should be added back to the queue. + */ + boolean afterExecute( long time ) + { + consumeTime( time ); + + synchronized( queueLock ) + { + virtualTime += time; + updateTime(); + if( state != State.COOL || tasks.isEmpty() ) return onQueue = false; + return true; + } + } + + /** + * Whether we should execute "external" tasks (ones not part of {@link #tasks}). + * + * @return Whether we can execute external tasks. + */ + @Override + public boolean canWork() + { + return state != State.COOLING && MainThread.canExecute(); + } + + @Override + public boolean shouldWork() + { + return state == State.COOL && MainThread.canExecute(); + } + + @Override + public void trackWork( long time, @Nonnull TimeUnit unit ) + { + long nanoTime = unit.toNanos( time ); + synchronized( queueLock ) + { + pendingTime += nanoTime; + } + + consumeTime( nanoTime ); + MainThread.consumeTime( nanoTime ); + } + + private void consumeTime( long time ) + { + Tracking.addServerTiming( computer, time ); + + // Reset the budget if moving onto a new tick. We know this is safe, as this will only have happened if + // #tickCooling() isn't called, and so we didn't overrun the previous tick. + if( currentTick != MainThread.currentTick() ) + { + currentTick = MainThread.currentTick(); + budget = ComputerCraft.maxMainComputerTime; + } + + budget -= time; + + // If we've gone over our limit, mark us as having to cool down. + if( budget < 0 && state == State.COOL ) + { + state = State.HOT; + MainThread.cooling( this ); + } + } + + /** + * Move this executor forward one tick, replenishing the budget by {@link ComputerCraft#maxMainComputerTime}. + * + * @return Whether this executor has cooled down, and so is safe to run again. + */ + boolean tickCooling() + { + state = State.COOLING; + currentTick = MainThread.currentTick(); + budget = Math.min( budget + ComputerCraft.maxMainComputerTime, ComputerCraft.maxMainComputerTime ); + if( budget < ComputerCraft.maxMainComputerTime ) return false; + + state = State.COOL; + synchronized( queueLock ) + { + if( !tasks.isEmpty() && !onQueue ) MainThread.queue( this, false ); + } + return true; + } + + void updateTime() + { + virtualTime += pendingTime; + pendingTime = 0; + } + + private enum State + { + COOL, + HOT, + COOLING, + } +} diff --git a/remappedSrc/dan200/computercraft/core/computer/TimeoutState.java b/remappedSrc/dan200/computercraft/core/computer/TimeoutState.java new file mode 100644 index 000000000..9e8b332ee --- /dev/null +++ b/remappedSrc/dan200/computercraft/core/computer/TimeoutState.java @@ -0,0 +1,168 @@ +/* + * 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.computer; + +import dan200.computercraft.core.lua.ILuaMachine; +import dan200.computercraft.core.lua.MachineResult; + +import java.util.concurrent.TimeUnit; + +/** + * Used to measure how long a computer has executed for, and thus the relevant timeout states. + * + * Timeouts are mostly used for execution of Lua code: we should ideally never have a state where constructing the APIs + * or machines themselves takes more than a fraction of a second. + * + * When a computer runs, it is allowed to run for 7 seconds ({@link #TIMEOUT}). After that point, the "soft abort" flag + * is set ({@link #isSoftAborted()}). Here, the Lua machine will attempt to abort the program in some safe manner + * (namely, throwing a "Too long without yielding" error). + * + * Now, if a computer still does not stop after that period, they're behaving really badly. 1.5 seconds after a soft + * abort ({@link #ABORT_TIMEOUT}), we trigger a hard abort (note, this is done from the computer thread manager). This + * will destroy the entire Lua runtime and shut the computer down. + * + * The Lua runtime is also allowed to pause execution if there are other computers contesting for work. All computers + * are allowed to run for {@link ComputerThread#scaledPeriod()} nanoseconds (see {@link #currentDeadline}). After that + * period, if any computers are waiting to be executed then we'll set the paused flag to true ({@link #isPaused()}. + * + * @see ComputerThread + * @see ILuaMachine + * @see MachineResult#isPause() + */ +public final class TimeoutState +{ + /** + * The total time a task is allowed to run before aborting in nanoseconds + */ + static final long TIMEOUT = TimeUnit.MILLISECONDS.toNanos( 7000 ); + + /** + * The time the task is allowed to run after each abort in nanoseconds + */ + static final long ABORT_TIMEOUT = TimeUnit.MILLISECONDS.toNanos( 1500 ); + + /** + * The error message to display when we trigger an abort. + */ + public static final String ABORT_MESSAGE = "Too long without yielding"; + + private boolean paused; + private boolean softAbort; + private volatile boolean hardAbort; + + /** + * When the cumulative time would have started had the whole event been processed in one go. + */ + private long cumulativeStart; + + /** + * How much cumulative time has elapsed. This is effectively {@code cumulativeStart - currentStart}. + */ + private long cumulativeElapsed; + + /** + * When this execution round started. + */ + private long currentStart; + + /** + * When this execution round should look potentially be paused. + */ + private long currentDeadline; + + long nanoCumulative() + { + return System.nanoTime() - cumulativeStart; + } + + long nanoCurrent() + { + return System.nanoTime() - currentStart; + } + + /** + * Recompute the {@link #isSoftAborted()} and {@link #isPaused()} flags. + */ + public void refresh() + { + // Important: The weird arithmetic here is important, as nanoTime may return negative values, and so we + // need to handle overflow. + long now = System.nanoTime(); + if( !paused ) paused = currentDeadline - now <= 0 && ComputerThread.hasPendingWork(); // now >= currentDeadline + if( !softAbort ) softAbort = now - cumulativeStart - TIMEOUT >= 0; // now - cumulativeStart >= TIMEOUT + } + + /** + * Whether we should pause execution of this machine. + * + * This is determined by whether we've consumed our time slice, and if there are other computers waiting to perform + * work. + * + * @return Whether we should pause execution. + */ + public boolean isPaused() + { + return paused; + } + + /** + * If the machine should be passively aborted. + */ + public boolean isSoftAborted() + { + return softAbort; + } + + /** + * If the machine should be forcibly aborted. + */ + public boolean isHardAborted() + { + return hardAbort; + } + + /** + * If the machine should be forcibly aborted. + */ + void hardAbort() + { + softAbort = hardAbort = true; + } + + /** + * Start the current and cumulative timers again. + */ + void startTimer() + { + long now = System.nanoTime(); + currentStart = now; + currentDeadline = now + ComputerThread.scaledPeriod(); + // Compute the "nominal start time". + cumulativeStart = now - cumulativeElapsed; + } + + /** + * Pauses the cumulative time, to be resumed by {@link #startTimer()} + * + * @see #nanoCumulative() + */ + void pauseTimer() + { + // We set the cumulative time to difference between current time and "nominal start time". + cumulativeElapsed = System.nanoTime() - cumulativeStart; + paused = false; + } + + /** + * Resets the cumulative time and resets the abort flags. + */ + void stopTimer() + { + cumulativeElapsed = 0; + paused = softAbort = hardAbort = false; + } +} diff --git a/remappedSrc/dan200/computercraft/core/filesystem/ChannelWrapper.java b/remappedSrc/dan200/computercraft/core/filesystem/ChannelWrapper.java new file mode 100644 index 000000000..ee4457032 --- /dev/null +++ b/remappedSrc/dan200/computercraft/core/filesystem/ChannelWrapper.java @@ -0,0 +1,50 @@ +/* + * 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.filesystem; + +import java.io.Closeable; +import java.io.IOException; +import java.nio.channels.Channel; + +/** + * Wraps some closeable object such as a buffered writer, and the underlying stream. + * + * When flushing a buffer before closing, some implementations will not close the buffer if an exception is thrown + * this causes us to release the channel, but not actually close it. This wrapper will attempt to close the wrapper (and + * so hopefully flush the channel), and then close the underlying channel. + * + * @param The type of the closeable object to write. + */ +class ChannelWrapper implements Closeable +{ + private final T wrapper; + private final Channel channel; + + ChannelWrapper( T wrapper, Channel channel ) + { + this.wrapper = wrapper; + this.channel = channel; + } + + @Override + public void close() throws IOException + { + try + { + wrapper.close(); + } + finally + { + channel.close(); + } + } + + public T get() + { + return wrapper; + } +} diff --git a/remappedSrc/dan200/computercraft/core/filesystem/ComboMount.java b/remappedSrc/dan200/computercraft/core/filesystem/ComboMount.java new file mode 100644 index 000000000..1dce543c1 --- /dev/null +++ b/remappedSrc/dan200/computercraft/core/filesystem/ComboMount.java @@ -0,0 +1,146 @@ +/* + * 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.filesystem; + +import dan200.computercraft.api.filesystem.IMount; + +import javax.annotation.Nonnull; +import java.io.IOException; +import java.io.InputStream; +import java.nio.channels.ReadableByteChannel; +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +public class ComboMount implements IMount +{ + private IMount[] m_parts; + + public ComboMount( IMount[] parts ) + { + m_parts = parts; + } + + // IMount implementation + + @Override + public boolean exists( @Nonnull String path ) throws IOException + { + for( int i = m_parts.length - 1; i >= 0; --i ) + { + IMount part = m_parts[i]; + if( part.exists( path ) ) + { + return true; + } + } + return false; + } + + @Override + public boolean isDirectory( @Nonnull String path ) throws IOException + { + for( int i = m_parts.length - 1; i >= 0; --i ) + { + IMount part = m_parts[i]; + if( part.isDirectory( path ) ) + { + return true; + } + } + return false; + } + + @Override + public void list( @Nonnull String path, @Nonnull List contents ) throws IOException + { + // Combine the lists from all the mounts + List foundFiles = null; + int foundDirs = 0; + for( int i = m_parts.length - 1; i >= 0; --i ) + { + IMount part = m_parts[i]; + if( part.exists( path ) && part.isDirectory( path ) ) + { + if( foundFiles == null ) + { + foundFiles = new ArrayList<>(); + } + part.list( path, foundFiles ); + foundDirs++; + } + } + + if( foundDirs == 1 ) + { + // We found one directory, so we know it already doesn't contain duplicates + contents.addAll( foundFiles ); + } + else if( foundDirs > 1 ) + { + // We found multiple directories, so filter for duplicates + Set seen = new HashSet<>(); + for( String file : foundFiles ) + { + if( seen.add( file ) ) + { + contents.add( file ); + } + } + } + else + { + throw new IOException( "/" + path + ": Not a directory" ); + } + } + + @Override + public long getSize( @Nonnull String path ) throws IOException + { + for( int i = m_parts.length - 1; i >= 0; --i ) + { + IMount part = m_parts[i]; + if( part.exists( path ) ) + { + return part.getSize( path ); + } + } + throw new IOException( "/" + path + ": No such file" ); + } + + @Nonnull + @Override + @Deprecated + public InputStream openForRead( @Nonnull String path ) throws IOException + { + for( int i = m_parts.length - 1; i >= 0; --i ) + { + IMount part = m_parts[i]; + if( part.exists( path ) && !part.isDirectory( path ) ) + { + return part.openForRead( path ); + } + } + throw new IOException( "/" + path + ": No such file" ); + } + + @Nonnull + @Override + public ReadableByteChannel openChannelForRead( @Nonnull String path ) throws IOException + { + for( int i = m_parts.length - 1; i >= 0; --i ) + { + IMount part = m_parts[i]; + if( part.exists( path ) && !part.isDirectory( path ) ) + { + return part.openChannelForRead( path ); + } + } + throw new IOException( "/" + path + ": No such file" ); + } +} diff --git a/remappedSrc/dan200/computercraft/core/filesystem/EmptyMount.java b/remappedSrc/dan200/computercraft/core/filesystem/EmptyMount.java new file mode 100644 index 000000000..634a76649 --- /dev/null +++ b/remappedSrc/dan200/computercraft/core/filesystem/EmptyMount.java @@ -0,0 +1,57 @@ +/* + * 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.filesystem; + +import dan200.computercraft.api.filesystem.IMount; + +import javax.annotation.Nonnull; +import java.io.IOException; +import java.io.InputStream; +import java.nio.channels.ReadableByteChannel; +import java.util.List; + +public class EmptyMount implements IMount +{ + @Override + public boolean exists( @Nonnull String path ) + { + return path.isEmpty(); + } + + @Override + public boolean isDirectory( @Nonnull String path ) + { + return path.isEmpty(); + } + + @Override + public void list( @Nonnull String path, @Nonnull List contents ) + { + } + + @Override + public long getSize( @Nonnull String path ) + { + return 0; + } + + @Nonnull + @Override + @Deprecated + public InputStream openForRead( @Nonnull String path ) throws IOException + { + throw new IOException( "/" + path + ": No such file" ); + } + + @Nonnull + @Override + @Deprecated + public ReadableByteChannel openChannelForRead( @Nonnull String path ) throws IOException + { + throw new IOException( "/" + path + ": No such file" ); + } +} diff --git a/remappedSrc/dan200/computercraft/core/filesystem/FileMount.java b/remappedSrc/dan200/computercraft/core/filesystem/FileMount.java new file mode 100644 index 000000000..45cad041e --- /dev/null +++ b/remappedSrc/dan200/computercraft/core/filesystem/FileMount.java @@ -0,0 +1,404 @@ +/* + * 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.filesystem; + +import com.google.common.collect.Sets; +import dan200.computercraft.api.filesystem.IWritableMount; + +import javax.annotation.Nonnull; +import java.io.*; +import java.nio.ByteBuffer; +import java.nio.channels.*; +import java.nio.file.Files; +import java.nio.file.OpenOption; +import java.nio.file.StandardOpenOption; +import java.util.Collections; +import java.util.List; +import java.util.Set; + +public class FileMount implements IWritableMount +{ + private static final int MINIMUM_FILE_SIZE = 500; + private static final Set READ_OPTIONS = Collections.singleton( StandardOpenOption.READ ); + private static final Set WRITE_OPTIONS = Sets.newHashSet( StandardOpenOption.WRITE, StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING ); + private static final Set APPEND_OPTIONS = Sets.newHashSet( StandardOpenOption.WRITE, StandardOpenOption.CREATE, StandardOpenOption.APPEND ); + + private class WritableCountingChannel implements WritableByteChannel + { + + private final WritableByteChannel m_inner; + long m_ignoredBytesLeft; + + WritableCountingChannel( WritableByteChannel inner, long bytesToIgnore ) + { + m_inner = inner; + m_ignoredBytesLeft = bytesToIgnore; + } + + @Override + public int write( @Nonnull ByteBuffer b ) throws IOException + { + count( b.remaining() ); + return m_inner.write( b ); + } + + void count( long n ) throws IOException + { + m_ignoredBytesLeft -= n; + if( m_ignoredBytesLeft < 0 ) + { + long newBytes = -m_ignoredBytesLeft; + m_ignoredBytesLeft = 0; + + long bytesLeft = m_capacity - m_usedSpace; + if( newBytes > bytesLeft ) throw new IOException( "Out of space" ); + m_usedSpace += newBytes; + } + } + + @Override + public boolean isOpen() + { + return m_inner.isOpen(); + } + + @Override + public void close() throws IOException + { + m_inner.close(); + } + } + + private class SeekableCountingChannel extends WritableCountingChannel implements SeekableByteChannel + { + private final SeekableByteChannel m_inner; + + SeekableCountingChannel( SeekableByteChannel inner, long bytesToIgnore ) + { + super( inner, bytesToIgnore ); + m_inner = inner; + } + + @Override + public SeekableByteChannel position( long newPosition ) throws IOException + { + if( !isOpen() ) throw new ClosedChannelException(); + if( newPosition < 0 ) + { + throw new IllegalArgumentException( "Cannot seek before the beginning of the stream" ); + } + + long delta = newPosition - m_inner.position(); + if( delta < 0 ) + { + m_ignoredBytesLeft -= delta; + } + else + { + count( delta ); + } + + return m_inner.position( newPosition ); + } + + @Override + public SeekableByteChannel truncate( long size ) throws IOException + { + throw new IOException( "Not yet implemented" ); + } + + @Override + public int read( ByteBuffer dst ) throws ClosedChannelException + { + if( !m_inner.isOpen() ) throw new ClosedChannelException(); + throw new NonReadableChannelException(); + } + + @Override + public long position() throws IOException + { + return m_inner.position(); + } + + @Override + public long size() throws IOException + { + return m_inner.size(); + } + } + + private File m_rootPath; + private long m_capacity; + private long m_usedSpace; + + public FileMount( File rootPath, long capacity ) + { + m_rootPath = rootPath; + m_capacity = capacity + MINIMUM_FILE_SIZE; + m_usedSpace = created() ? measureUsedSpace( m_rootPath ) : MINIMUM_FILE_SIZE; + } + + // IMount implementation + + @Override + public boolean exists( @Nonnull String path ) + { + if( !created() ) return path.isEmpty(); + + File file = getRealPath( path ); + return file.exists(); + } + + @Override + public boolean isDirectory( @Nonnull String path ) + { + if( !created() ) return path.isEmpty(); + + File file = getRealPath( path ); + return file.exists() && file.isDirectory(); + } + + @Override + public void list( @Nonnull String path, @Nonnull List contents ) throws IOException + { + if( !created() ) + { + if( !path.isEmpty() ) throw new IOException( "/" + path + ": Not a directory" ); + return; + } + + File file = getRealPath( path ); + if( !file.exists() || !file.isDirectory() ) throw new IOException( "/" + path + ": Not a directory" ); + + String[] paths = file.list(); + for( String subPath : paths ) + { + if( new File( file, subPath ).exists() ) contents.add( subPath ); + } + } + + @Override + public long getSize( @Nonnull String path ) throws IOException + { + if( !created() ) + { + if( path.isEmpty() ) return 0; + } + else + { + File file = getRealPath( path ); + if( file.exists() ) return file.isDirectory() ? 0 : file.length(); + } + + throw new IOException( "/" + path + ": No such file" ); + } + + @Nonnull + @Override + @Deprecated + public InputStream openForRead( @Nonnull String path ) throws IOException + { + if( created() ) + { + File file = getRealPath( path ); + if( file.exists() && !file.isDirectory() ) return new FileInputStream( file ); + } + + throw new IOException( "/" + path + ": No such file" ); + } + + @Nonnull + @Override + public ReadableByteChannel openChannelForRead( @Nonnull String path ) throws IOException + { + if( created() ) + { + File file = getRealPath( path ); + if( file.exists() && !file.isDirectory() ) return FileChannel.open( file.toPath(), READ_OPTIONS ); + } + + throw new IOException( "/" + path + ": No such file" ); + } + + // IWritableMount implementation + + @Override + public void makeDirectory( @Nonnull String path ) throws IOException + { + create(); + File file = getRealPath( path ); + if( file.exists() ) + { + if( !file.isDirectory() ) throw new IOException( "/" + path + ": File exists" ); + return; + } + + int dirsToCreate = 1; + File parent = file.getParentFile(); + while( !parent.exists() ) + { + ++dirsToCreate; + parent = parent.getParentFile(); + } + + if( getRemainingSpace() < dirsToCreate * MINIMUM_FILE_SIZE ) + { + throw new IOException( "/" + path + ": Out of space" ); + } + + if( file.mkdirs() ) + { + m_usedSpace += dirsToCreate * MINIMUM_FILE_SIZE; + } + else + { + throw new IOException( "/" + path + ": Access denied" ); + } + } + + @Override + public void delete( @Nonnull String path ) throws IOException + { + if( path.isEmpty() ) throw new IOException( "/" + path + ": Access denied" ); + + if( created() ) + { + File file = getRealPath( path ); + if( file.exists() ) deleteRecursively( file ); + } + } + + private void deleteRecursively( File file ) throws IOException + { + // Empty directories first + if( file.isDirectory() ) + { + String[] children = file.list(); + for( String aChildren : children ) + { + deleteRecursively( new File( file, aChildren ) ); + } + } + + // Then delete + long fileSize = file.isDirectory() ? 0 : file.length(); + boolean success = file.delete(); + if( success ) + { + m_usedSpace -= Math.max( MINIMUM_FILE_SIZE, fileSize ); + } + else + { + throw new IOException( "Access denied" ); + } + } + + @Nonnull + @Override + @Deprecated + public OutputStream openForWrite( @Nonnull String path ) throws IOException + { + return Channels.newOutputStream( openChannelForWrite( path ) ); + } + + @Nonnull + @Override + @Deprecated + public OutputStream openForAppend( @Nonnull String path ) throws IOException + { + return Channels.newOutputStream( openChannelForAppend( path ) ); + } + + @Nonnull + @Override + public WritableByteChannel openChannelForWrite( @Nonnull String path ) throws IOException + { + create(); + File file = getRealPath( path ); + if( file.exists() && file.isDirectory() ) throw new IOException( "/" + path + ": Cannot write to directory" ); + + if( file.exists() ) + { + m_usedSpace -= Math.max( file.length(), MINIMUM_FILE_SIZE ); + } + else if( getRemainingSpace() < MINIMUM_FILE_SIZE ) + { + throw new IOException( "/" + path + ": Out of space" ); + } + m_usedSpace += MINIMUM_FILE_SIZE; + + return new SeekableCountingChannel( Files.newByteChannel( file.toPath(), WRITE_OPTIONS ), MINIMUM_FILE_SIZE ); + } + + @Nonnull + @Override + public WritableByteChannel openChannelForAppend( @Nonnull String path ) throws IOException + { + if( !created() ) + { + throw new IOException( "/" + path + ": No such file" ); + } + + File file = getRealPath( path ); + if( !file.exists() ) throw new IOException( "/" + path + ": No such file" ); + if( file.isDirectory() ) throw new IOException( "/" + path + ": Cannot write to directory" ); + + // Allowing seeking when appending is not recommended, so we use a separate channel. + return new WritableCountingChannel( + Files.newByteChannel( file.toPath(), APPEND_OPTIONS ), + Math.max( MINIMUM_FILE_SIZE - file.length(), 0 ) + ); + } + + @Override + public long getRemainingSpace() + { + return Math.max( m_capacity - m_usedSpace, 0 ); + } + + private File getRealPath( String path ) + { + return new File( m_rootPath, path ); + } + + private boolean created() + { + return m_rootPath.exists(); + } + + private void create() throws IOException + { + if( !m_rootPath.exists() ) + { + boolean success = m_rootPath.mkdirs(); + if( !success ) + { + throw new IOException( "Access denied" ); + } + } + } + + private static long measureUsedSpace( File file ) + { + if( !file.exists() ) return 0; + + if( file.isDirectory() ) + { + long size = MINIMUM_FILE_SIZE; + String[] contents = file.list(); + for( String content : contents ) + { + size += measureUsedSpace( new File( file, content ) ); + } + return size; + } + else + { + return Math.max( file.length(), MINIMUM_FILE_SIZE ); + } + } +} diff --git a/remappedSrc/dan200/computercraft/core/filesystem/FileSystem.java b/remappedSrc/dan200/computercraft/core/filesystem/FileSystem.java new file mode 100644 index 000000000..e36d41af3 --- /dev/null +++ b/remappedSrc/dan200/computercraft/core/filesystem/FileSystem.java @@ -0,0 +1,889 @@ +/* + * 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.filesystem; + +import com.google.common.io.ByteStreams; +import dan200.computercraft.ComputerCraft; +import dan200.computercraft.api.filesystem.IFileSystem; +import dan200.computercraft.api.filesystem.IMount; +import dan200.computercraft.api.filesystem.IWritableMount; +import dan200.computercraft.shared.util.IoUtil; + +import javax.annotation.Nonnull; +import java.io.Closeable; +import java.io.IOException; +import java.lang.ref.Reference; +import java.lang.ref.ReferenceQueue; +import java.lang.ref.WeakReference; +import java.nio.channels.Channel; +import java.nio.channels.ReadableByteChannel; +import java.nio.channels.WritableByteChannel; +import java.nio.file.AccessDeniedException; +import java.util.*; +import java.util.function.Function; +import java.util.regex.Pattern; + +public class FileSystem +{ + private static class MountWrapper + { + private String m_label; + private String m_location; + + private IMount m_mount; + private IWritableMount m_writableMount; + + MountWrapper( String label, String location, IMount mount ) + { + m_label = label; + m_location = location; + m_mount = mount; + m_writableMount = null; + } + + public MountWrapper( String label, String location, IWritableMount mount ) + { + this( label, location, (IMount) mount ); + m_writableMount = mount; + } + + public String getLabel() + { + return m_label; + } + + public String getLocation() + { + return m_location; + } + + public long getFreeSpace() + { + if( m_writableMount == null ) + { + return 0; + } + + try + { + return m_writableMount.getRemainingSpace(); + } + catch( IOException e ) + { + return 0; + } + } + + public boolean isReadOnly( String path ) + { + return m_writableMount == null; + } + + // IMount forwarders: + + public boolean exists( String path ) throws FileSystemException + { + path = toLocal( path ); + try + { + return m_mount.exists( path ); + } + catch( IOException e ) + { + throw new FileSystemException( e.getMessage() ); + } + } + + public boolean isDirectory( String path ) throws FileSystemException + { + path = toLocal( path ); + try + { + return m_mount.exists( path ) && m_mount.isDirectory( path ); + } + catch( IOException e ) + { + throw new FileSystemException( e.getMessage() ); + } + } + + public void list( String path, List contents ) throws FileSystemException + { + path = toLocal( path ); + try + { + if( m_mount.exists( path ) && m_mount.isDirectory( path ) ) + { + m_mount.list( path, contents ); + } + else + { + throw new FileSystemException( "/" + path + ": Not a directory" ); + } + } + catch( IOException e ) + { + throw new FileSystemException( e.getMessage() ); + } + } + + public long getSize( String path ) throws FileSystemException + { + path = toLocal( path ); + try + { + if( m_mount.exists( path ) ) + { + if( m_mount.isDirectory( path ) ) + { + return 0; + } + else + { + return m_mount.getSize( path ); + } + } + else + { + throw new FileSystemException( "/" + path + ": No such file" ); + } + } + catch( IOException e ) + { + throw new FileSystemException( e.getMessage() ); + } + } + + public ReadableByteChannel openForRead( String path ) throws FileSystemException + { + path = toLocal( path ); + try + { + if( m_mount.exists( path ) && !m_mount.isDirectory( path ) ) + { + return m_mount.openChannelForRead( path ); + } + else + { + throw new FileSystemException( "/" + path + ": No such file" ); + } + } + catch( IOException e ) + { + throw new FileSystemException( e.getMessage() ); + } + } + + // IWritableMount forwarders: + + public void makeDirectory( String path ) throws FileSystemException + { + if( m_writableMount == null ) + { + throw new FileSystemException( "/" + path + ": Access denied" ); + } + try + { + path = toLocal( path ); + if( m_mount.exists( path ) ) + { + if( !m_mount.isDirectory( path ) ) + { + throw new FileSystemException( "/" + path + ": File exists" ); + } + } + else + { + m_writableMount.makeDirectory( path ); + } + } + catch( IOException e ) + { + throw new FileSystemException( e.getMessage() ); + } + } + + public void delete( String path ) throws FileSystemException + { + if( m_writableMount == null ) + { + throw new FileSystemException( "/" + path + ": Access denied" ); + } + try + { + path = toLocal( path ); + if( m_mount.exists( path ) ) + { + m_writableMount.delete( path ); + } + } + catch( AccessDeniedException e ) + { + throw new FileSystemException( "Access denied" ); + } + catch( IOException e ) + { + throw new FileSystemException( e.getMessage() ); + } + } + + public WritableByteChannel openForWrite( String path ) throws FileSystemException + { + if( m_writableMount == null ) + { + throw new FileSystemException( "/" + path + ": Access denied" ); + } + try + { + path = toLocal( path ); + if( m_mount.exists( path ) && m_mount.isDirectory( path ) ) + { + throw new FileSystemException( "/" + path + ": Cannot write to directory" ); + } + else + { + if( !path.isEmpty() ) + { + String dir = getDirectory( path ); + if( !dir.isEmpty() && !m_mount.exists( path ) ) + { + m_writableMount.makeDirectory( dir ); + } + } + return m_writableMount.openChannelForWrite( path ); + } + } + catch( AccessDeniedException e ) + { + throw new FileSystemException( "Access denied" ); + } + catch( IOException e ) + { + throw new FileSystemException( e.getMessage() ); + } + } + + public WritableByteChannel openForAppend( String path ) throws FileSystemException + { + if( m_writableMount == null ) + { + throw new FileSystemException( "/" + path + ": Access denied" ); + } + try + { + path = toLocal( path ); + if( !m_mount.exists( path ) ) + { + if( !path.isEmpty() ) + { + String dir = getDirectory( path ); + if( !dir.isEmpty() && !m_mount.exists( path ) ) + { + m_writableMount.makeDirectory( dir ); + } + } + return m_writableMount.openChannelForWrite( path ); + } + else if( m_mount.isDirectory( path ) ) + { + throw new FileSystemException( "/" + path + ": Cannot write to directory" ); + } + else + { + return m_writableMount.openChannelForAppend( path ); + } + } + catch( AccessDeniedException e ) + { + throw new FileSystemException( "Access denied" ); + } + catch( IOException e ) + { + throw new FileSystemException( e.getMessage() ); + } + } + + // private members + + private String toLocal( String path ) + { + return FileSystem.toLocal( path, m_location ); + } + } + + private final FileSystemWrapperMount m_wrapper = new FileSystemWrapperMount( this ); + private final Map m_mounts = new HashMap<>(); + + private final HashMap>, ChannelWrapper> m_openFiles = new HashMap<>(); + private final ReferenceQueue> m_openFileQueue = new ReferenceQueue<>(); + + public FileSystem( String rootLabel, IMount rootMount ) throws FileSystemException + { + mount( rootLabel, "", rootMount ); + } + + public FileSystem( String rootLabel, IWritableMount rootMount ) throws FileSystemException + { + mountWritable( rootLabel, "", rootMount ); + } + + public void close() + { + // Close all dangling open files + synchronized( m_openFiles ) + { + for( Closeable file : m_openFiles.values() ) IoUtil.closeQuietly( file ); + m_openFiles.clear(); + while( m_openFileQueue.poll() != null ) ; + } + } + + public synchronized void mount( String label, String location, IMount mount ) throws FileSystemException + { + if( mount == null ) throw new NullPointerException(); + location = sanitizePath( location ); + if( location.contains( ".." ) ) + { + throw new FileSystemException( "Cannot mount below the root" ); + } + mount( new MountWrapper( label, location, mount ) ); + } + + public synchronized void mountWritable( String label, String location, IWritableMount mount ) throws FileSystemException + { + if( mount == null ) + { + throw new NullPointerException(); + } + location = sanitizePath( location ); + if( location.contains( ".." ) ) + { + throw new FileSystemException( "Cannot mount below the root" ); + } + mount( new MountWrapper( label, location, mount ) ); + } + + private synchronized void mount( MountWrapper wrapper ) + { + String location = wrapper.getLocation(); + m_mounts.remove( location ); + m_mounts.put( location, wrapper ); + } + + public synchronized void unmount( String path ) + { + path = sanitizePath( path ); + m_mounts.remove( path ); + } + + public synchronized String combine( String path, String childPath ) + { + path = sanitizePath( path, true ); + childPath = sanitizePath( childPath, true ); + + if( path.isEmpty() ) + { + return childPath; + } + else if( childPath.isEmpty() ) + { + return path; + } + else + { + return sanitizePath( path + '/' + childPath, true ); + } + } + + public static String getDirectory( String path ) + { + path = sanitizePath( path, true ); + if( path.isEmpty() ) + { + return ".."; + } + + int lastSlash = path.lastIndexOf( '/' ); + if( lastSlash >= 0 ) + { + return path.substring( 0, lastSlash ); + } + else + { + return ""; + } + } + + public static String getName( String path ) + { + path = sanitizePath( path, true ); + if( path.isEmpty() ) + { + return "root"; + } + + int lastSlash = path.lastIndexOf( '/' ); + if( lastSlash >= 0 ) + { + return path.substring( lastSlash + 1 ); + } + else + { + return path; + } + } + + public synchronized long getSize( String path ) throws FileSystemException + { + path = sanitizePath( path ); + MountWrapper mount = getMount( path ); + return mount.getSize( path ); + } + + public synchronized String[] list( String path ) throws FileSystemException + { + path = sanitizePath( path ); + MountWrapper mount = getMount( path ); + + // Gets a list of the files in the mount + List list = new ArrayList<>(); + mount.list( path, list ); + + // Add any mounts that are mounted at this location + for( MountWrapper otherMount : m_mounts.values() ) + { + if( getDirectory( otherMount.getLocation() ).equals( path ) ) + { + list.add( getName( otherMount.getLocation() ) ); + } + } + + // Return list + String[] array = new String[list.size()]; + list.toArray( array ); + Arrays.sort( array ); + return array; + } + + private void findIn( String dir, List matches, Pattern wildPattern ) throws FileSystemException + { + String[] list = list( dir ); + for( String entry : list ) + { + String entryPath = dir.isEmpty() ? entry : dir + "/" + entry; + if( wildPattern.matcher( entryPath ).matches() ) + { + matches.add( entryPath ); + } + if( isDir( entryPath ) ) + { + findIn( entryPath, matches, wildPattern ); + } + } + } + + public synchronized String[] find( String wildPath ) throws FileSystemException + { + // Match all the files on the system + wildPath = sanitizePath( wildPath, true ); + + // If we don't have a wildcard at all just check the file exists + int starIndex = wildPath.indexOf( '*' ); + if( starIndex == -1 ) + { + return exists( wildPath ) ? new String[] { wildPath } : new String[0]; + } + + // Find the all non-wildcarded directories. For instance foo/bar/baz* -> foo/bar + int prevDir = wildPath.substring( 0, starIndex ).lastIndexOf( '/' ); + String startDir = prevDir == -1 ? "" : wildPath.substring( 0, prevDir ); + + // If this isn't a directory then just abort + if( !isDir( startDir ) ) return new String[0]; + + // Scan as normal, starting from this directory + Pattern wildPattern = Pattern.compile( "^\\Q" + wildPath.replaceAll( "\\*", "\\\\E[^\\\\/]*\\\\Q" ) + "\\E$" ); + List matches = new ArrayList<>(); + findIn( startDir, matches, wildPattern ); + + // Return matches + String[] array = new String[matches.size()]; + matches.toArray( array ); + return array; + } + + public synchronized boolean exists( String path ) throws FileSystemException + { + path = sanitizePath( path ); + MountWrapper mount = getMount( path ); + return mount.exists( path ); + } + + public synchronized boolean isDir( String path ) throws FileSystemException + { + path = sanitizePath( path ); + MountWrapper mount = getMount( path ); + return mount.isDirectory( path ); + } + + public synchronized boolean isReadOnly( String path ) throws FileSystemException + { + path = sanitizePath( path ); + MountWrapper mount = getMount( path ); + return mount.isReadOnly( path ); + } + + public synchronized String getMountLabel( String path ) throws FileSystemException + { + path = sanitizePath( path ); + MountWrapper mount = getMount( path ); + return mount.getLabel(); + } + + public synchronized void makeDir( String path ) throws FileSystemException + { + path = sanitizePath( path ); + MountWrapper mount = getMount( path ); + mount.makeDirectory( path ); + } + + public synchronized void delete( String path ) throws FileSystemException + { + path = sanitizePath( path ); + MountWrapper mount = getMount( path ); + mount.delete( path ); + } + + public synchronized void move( String sourcePath, String destPath ) throws FileSystemException + { + sourcePath = sanitizePath( sourcePath ); + destPath = sanitizePath( destPath ); + if( isReadOnly( sourcePath ) || isReadOnly( destPath ) ) + { + throw new FileSystemException( "Access denied" ); + } + if( !exists( sourcePath ) ) + { + throw new FileSystemException( "No such file" ); + } + if( exists( destPath ) ) + { + throw new FileSystemException( "File exists" ); + } + if( contains( sourcePath, destPath ) ) + { + throw new FileSystemException( "Can't move a directory inside itself" ); + } + copy( sourcePath, destPath ); + delete( sourcePath ); + } + + public synchronized void copy( String sourcePath, String destPath ) throws FileSystemException + { + sourcePath = sanitizePath( sourcePath ); + destPath = sanitizePath( destPath ); + if( isReadOnly( destPath ) ) + { + throw new FileSystemException( "/" + destPath + ": Access denied" ); + } + if( !exists( sourcePath ) ) + { + throw new FileSystemException( "/" + sourcePath + ": No such file" ); + } + if( exists( destPath ) ) + { + throw new FileSystemException( "/" + destPath + ": File exists" ); + } + if( contains( sourcePath, destPath ) ) + { + throw new FileSystemException( "/" + sourcePath + ": Can't copy a directory inside itself" ); + } + copyRecursive( sourcePath, getMount( sourcePath ), destPath, getMount( destPath ) ); + } + + private synchronized void copyRecursive( String sourcePath, MountWrapper sourceMount, String destinationPath, MountWrapper destinationMount ) throws FileSystemException + { + if( !sourceMount.exists( sourcePath ) ) + { + return; + } + + if( sourceMount.isDirectory( sourcePath ) ) + { + // Copy a directory: + // Make the new directory + destinationMount.makeDirectory( destinationPath ); + + // Copy the source contents into it + List sourceChildren = new ArrayList<>(); + sourceMount.list( sourcePath, sourceChildren ); + for( String child : sourceChildren ) + { + copyRecursive( + combine( sourcePath, child ), sourceMount, + combine( destinationPath, child ), destinationMount + ); + } + } + else + { + // Copy a file: + try( ReadableByteChannel source = sourceMount.openForRead( sourcePath ); + WritableByteChannel destination = destinationMount.openForWrite( destinationPath ) ) + { + // Copy bytes as fast as we can + ByteStreams.copy( source, destination ); + } + catch( AccessDeniedException e ) + { + throw new FileSystemException( "Access denied" ); + } + catch( IOException e ) + { + throw new FileSystemException( e.getMessage() ); + } + } + } + + private void cleanup() + { + synchronized( m_openFiles ) + { + Reference ref; + while( (ref = m_openFileQueue.poll()) != null ) + { + Closeable file = m_openFiles.remove( ref ); + if( file != null ) IoUtil.closeQuietly( file ); + } + } + } + + private synchronized FileSystemWrapper openFile( @Nonnull Channel channel, @Nonnull T file ) throws FileSystemException + { + synchronized( m_openFiles ) + { + if( ComputerCraft.maximumFilesOpen > 0 && + m_openFiles.size() >= ComputerCraft.maximumFilesOpen ) + { + IoUtil.closeQuietly( file ); + IoUtil.closeQuietly( channel ); + throw new FileSystemException( "Too many files already open" ); + } + + ChannelWrapper channelWrapper = new ChannelWrapper<>( file, channel ); + FileSystemWrapper fsWrapper = new FileSystemWrapper<>( this, channelWrapper, m_openFileQueue ); + m_openFiles.put( fsWrapper.self, channelWrapper ); + return fsWrapper; + } + } + + synchronized void removeFile( FileSystemWrapper handle ) + { + synchronized( m_openFiles ) + { + m_openFiles.remove( handle.self ); + } + } + + public synchronized FileSystemWrapper openForRead( String path, Function open ) throws FileSystemException + { + cleanup(); + + path = sanitizePath( path ); + MountWrapper mount = getMount( path ); + ReadableByteChannel channel = mount.openForRead( path ); + if( channel != null ) + { + return openFile( channel, open.apply( channel ) ); + } + return null; + } + + public synchronized FileSystemWrapper openForWrite( String path, boolean append, Function open ) throws FileSystemException + { + cleanup(); + + path = sanitizePath( path ); + MountWrapper mount = getMount( path ); + WritableByteChannel channel = append ? mount.openForAppend( path ) : mount.openForWrite( path ); + if( channel != null ) + { + return openFile( channel, open.apply( channel ) ); + } + return null; + } + + public long getFreeSpace( String path ) throws FileSystemException + { + path = sanitizePath( path ); + MountWrapper mount = getMount( path ); + return mount.getFreeSpace(); + } + + private MountWrapper getMount( String path ) throws FileSystemException + { + // Return the deepest mount that contains a given path + Iterator it = m_mounts.values().iterator(); + MountWrapper match = null; + int matchLength = 999; + while( it.hasNext() ) + { + MountWrapper mount = it.next(); + if( contains( mount.getLocation(), path ) ) + { + int len = toLocal( path, mount.getLocation() ).length(); + if( match == null || len < matchLength ) + { + match = mount; + matchLength = len; + } + } + } + if( match == null ) + { + throw new FileSystemException( "/" + path + ": Invalid Path" ); + } + return match; + } + + public IFileSystem getMountWrapper() + { + return m_wrapper; + } + + private static String sanitizePath( String path ) + { + return sanitizePath( path, false ); + } + + private static final Pattern threeDotsPattern = Pattern.compile( "^\\.{3,}$" ); + + private static String sanitizePath( String path, boolean allowWildcards ) + { + // Allow windowsy slashes + path = path.replace( '\\', '/' ); + + // Clean the path or illegal characters. + final char[] specialChars = new char[] { + '"', ':', '<', '>', '?', '|' // Sorted by ascii value (important) + }; + + StringBuilder cleanName = new StringBuilder(); + for( int i = 0; i < path.length(); i++ ) + { + char c = path.charAt( i ); + if( c >= 32 && Arrays.binarySearch( specialChars, c ) < 0 && (allowWildcards || c != '*') ) + { + cleanName.append( c ); + } + } + path = cleanName.toString(); + + // Collapse the string into its component parts, removing ..'s + String[] parts = path.split( "/" ); + Stack outputParts = new Stack<>(); + for( String part : parts ) + { + if( part.isEmpty() || part.equals( "." ) || threeDotsPattern.matcher( part ).matches() ) + { + // . is redundant + // ... and more are treated as . + continue; + } + + if( part.equals( ".." ) ) + { + // .. can cancel out the last folder entered + if( !outputParts.empty() ) + { + String top = outputParts.peek(); + if( !top.equals( ".." ) ) + { + outputParts.pop(); + } + else + { + outputParts.push( ".." ); + } + } + else + { + outputParts.push( ".." ); + } + } + else if( part.length() >= 255 ) + { + // If part length > 255 and it is the last part + outputParts.push( part.substring( 0, 255 ) ); + } + else + { + // Anything else we add to the stack + outputParts.push( part ); + } + } + + // Recombine the output parts into a new string + StringBuilder result = new StringBuilder(); + Iterator it = outputParts.iterator(); + while( it.hasNext() ) + { + String part = it.next(); + result.append( part ); + if( it.hasNext() ) + { + result.append( '/' ); + } + } + + return result.toString(); + } + + public static boolean contains( String pathA, String pathB ) + { + pathA = sanitizePath( pathA ); + pathB = sanitizePath( pathB ); + + if( pathB.equals( ".." ) ) + { + return false; + } + else if( pathB.startsWith( "../" ) ) + { + return false; + } + else if( pathB.equals( pathA ) ) + { + return true; + } + else if( pathA.isEmpty() ) + { + return true; + } + else + { + return pathB.startsWith( pathA + "/" ); + } + } + + public static String toLocal( String path, String location ) + { + path = sanitizePath( path ); + location = sanitizePath( location ); + + assert contains( location, path ); + String local = path.substring( location.length() ); + if( local.startsWith( "/" ) ) + { + return local.substring( 1 ); + } + else + { + return local; + } + } +} diff --git a/remappedSrc/dan200/computercraft/core/filesystem/FileSystemException.java b/remappedSrc/dan200/computercraft/core/filesystem/FileSystemException.java new file mode 100644 index 000000000..d4702e278 --- /dev/null +++ b/remappedSrc/dan200/computercraft/core/filesystem/FileSystemException.java @@ -0,0 +1,17 @@ +/* + * 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.filesystem; + +public class FileSystemException extends Exception +{ + private static final long serialVersionUID = -2500631644868104029L; + + FileSystemException( String s ) + { + super( s ); + } +} diff --git a/remappedSrc/dan200/computercraft/core/filesystem/FileSystemWrapper.java b/remappedSrc/dan200/computercraft/core/filesystem/FileSystemWrapper.java new file mode 100644 index 000000000..a51377f7e --- /dev/null +++ b/remappedSrc/dan200/computercraft/core/filesystem/FileSystemWrapper.java @@ -0,0 +1,53 @@ +/* + * 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.filesystem; + +import javax.annotation.Nonnull; +import java.io.Closeable; +import java.io.IOException; +import java.lang.ref.ReferenceQueue; +import java.lang.ref.WeakReference; + +/** + * An alternative closeable implementation that will free up resources in the filesystem. + * + * The {@link FileSystem} maps weak references of this to its underlying object. If the wrapper has been disposed of + * (say, the Lua object referencing it has gone), then the wrapped object will be closed by the filesystem. + * + * Closing this will stop the filesystem tracking it, reducing the current descriptor count. + * + * In an ideal world, we'd just wrap the closeable. However, as we do some {@code instanceof} checks + * on the stream, it's not really possible as it'd require numerous instances. + * + * @param The type of writer or channel to wrap. + */ +public class FileSystemWrapper implements Closeable +{ + private final FileSystem fileSystem; + private final ChannelWrapper closeable; + final WeakReference> self; + + FileSystemWrapper( FileSystem fileSystem, ChannelWrapper closeable, ReferenceQueue> queue ) + { + this.fileSystem = fileSystem; + this.closeable = closeable; + self = new WeakReference<>( this, queue ); + } + + @Override + public void close() throws IOException + { + fileSystem.removeFile( this ); + closeable.close(); + } + + @Nonnull + public T get() + { + return closeable.get(); + } +} diff --git a/remappedSrc/dan200/computercraft/core/filesystem/FileSystemWrapperMount.java b/remappedSrc/dan200/computercraft/core/filesystem/FileSystemWrapperMount.java new file mode 100644 index 000000000..01b110dc3 --- /dev/null +++ b/remappedSrc/dan200/computercraft/core/filesystem/FileSystemWrapperMount.java @@ -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.filesystem; + +import dan200.computercraft.api.filesystem.IFileSystem; + +import javax.annotation.Nonnull; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.nio.channels.Channels; +import java.nio.channels.ReadableByteChannel; +import java.nio.channels.WritableByteChannel; +import java.util.Collections; +import java.util.List; +import java.util.function.Function; + +public class FileSystemWrapperMount implements IFileSystem +{ + private final FileSystem m_filesystem; + + public FileSystemWrapperMount( FileSystem filesystem ) + { + this.m_filesystem = filesystem; + } + + @Override + public void makeDirectory( @Nonnull String path ) throws IOException + { + try + { + m_filesystem.makeDir( path ); + } + catch( FileSystemException e ) + { + throw new IOException( e.getMessage() ); + } + } + + @Override + public void delete( @Nonnull String path ) throws IOException + { + try + { + m_filesystem.delete( path ); + } + catch( FileSystemException e ) + { + throw new IOException( e.getMessage() ); + } + } + + @Nonnull + @Override + public ReadableByteChannel openChannelForRead( @Nonnull String path ) throws IOException + { + try + { + // FIXME: Think of a better way of implementing this, so closing this will close on the computer. + return m_filesystem.openForRead( path, Function.identity() ).get(); + } + catch( FileSystemException e ) + { + throw new IOException( e.getMessage() ); + } + } + + @Nonnull + @Override + public WritableByteChannel openChannelForWrite( @Nonnull String path ) throws IOException + { + try + { + return m_filesystem.openForWrite( path, false, Function.identity() ).get(); + } + catch( FileSystemException e ) + { + throw new IOException( e.getMessage() ); + } + } + + @Nonnull + @Override + public WritableByteChannel openChannelForAppend( @Nonnull String path ) throws IOException + { + try + { + return m_filesystem.openForWrite( path, true, Function.identity() ).get(); + } + catch( FileSystemException e ) + { + throw new IOException( e.getMessage() ); + } + } + + @Nonnull + @Override + @Deprecated + public InputStream openForRead( @Nonnull String path ) throws IOException + { + return Channels.newInputStream( openChannelForRead( path ) ); + } + + @Nonnull + @Override + @Deprecated + public OutputStream openForWrite( @Nonnull String path ) throws IOException + { + return Channels.newOutputStream( openChannelForWrite( path ) ); + } + + @Nonnull + @Override + @Deprecated + public OutputStream openForAppend( @Nonnull String path ) throws IOException + { + return Channels.newOutputStream( openChannelForAppend( path ) ); + } + + @Override + public long getRemainingSpace() throws IOException + { + try + { + return m_filesystem.getFreeSpace( "/" ); + } + catch( FileSystemException e ) + { + throw new IOException( e.getMessage() ); + } + } + + @Override + public boolean exists( @Nonnull String path ) throws IOException + { + try + { + return m_filesystem.exists( path ); + } + catch( FileSystemException e ) + { + throw new IOException( e.getMessage() ); + } + } + + @Override + public boolean isDirectory( @Nonnull String path ) throws IOException + { + try + { + return m_filesystem.exists( path ); + } + catch( FileSystemException e ) + { + throw new IOException( e.getMessage() ); + } + } + + @Override + public void list( @Nonnull String path, @Nonnull List contents ) throws IOException + { + try + { + Collections.addAll( contents, m_filesystem.list( path ) ); + } + catch( FileSystemException e ) + { + throw new IOException( e.getMessage() ); + } + } + + @Override + public long getSize( @Nonnull String path ) throws IOException + { + try + { + return m_filesystem.getSize( path ); + } + catch( FileSystemException e ) + { + throw new IOException( e.getMessage() ); + } + } + + @Override + public String combine( String path, String child ) + { + return m_filesystem.combine( path, child ); + } + + @Override + public void copy( String from, String to ) throws IOException + { + try + { + m_filesystem.copy( from, to ); + } + catch( FileSystemException e ) + { + throw new IOException( e.getMessage() ); + } + } + + @Override + public void move( String from, String to ) throws IOException + { + try + { + m_filesystem.move( from, to ); + } + catch( FileSystemException e ) + { + throw new IOException( e.getMessage() ); + } + } +} diff --git a/remappedSrc/dan200/computercraft/core/filesystem/JarMount.java b/remappedSrc/dan200/computercraft/core/filesystem/JarMount.java new file mode 100644 index 000000000..941837c94 --- /dev/null +++ b/remappedSrc/dan200/computercraft/core/filesystem/JarMount.java @@ -0,0 +1,271 @@ +/* + * 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.filesystem; + +import com.google.common.cache.Cache; +import com.google.common.cache.CacheBuilder; +import com.google.common.io.ByteStreams; +import dan200.computercraft.api.filesystem.IMount; +import dan200.computercraft.core.apis.handles.ArrayByteChannel; +import dan200.computercraft.shared.util.IoUtil; + +import javax.annotation.Nonnull; +import java.io.File; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.io.InputStream; +import java.lang.ref.Reference; +import java.lang.ref.ReferenceQueue; +import java.lang.ref.WeakReference; +import java.nio.channels.Channels; +import java.nio.channels.ReadableByteChannel; +import java.util.Enumeration; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.concurrent.TimeUnit; +import java.util.zip.ZipEntry; +import java.util.zip.ZipFile; + +public class JarMount implements IMount +{ + /** + * Only cache files smaller than 1MiB. + */ + private static final int MAX_CACHED_SIZE = 1 << 20; + + /** + * Limit the entire cache to 64MiB. + */ + private static final int MAX_CACHE_SIZE = 64 << 20; + + /** + * We maintain a cache of the contents of all files in the mount. This allows us to allow + * seeking within ROM files, and reduces the amount we need to access disk for computer startup. + */ + private static final Cache CONTENTS_CACHE = CacheBuilder.newBuilder() + .concurrencyLevel( 4 ) + .expireAfterAccess( 60, TimeUnit.SECONDS ) + .maximumWeight( MAX_CACHE_SIZE ) + .weakKeys() + .weigher( ( k, v ) -> v.length ) + .build(); + + /** + * We have a {@link ReferenceQueue} of all mounts, a long with their corresponding {@link ZipFile}. If + * the mount has been destroyed, we clean up after it. + */ + private static final ReferenceQueue MOUNT_QUEUE = new ReferenceQueue<>(); + + private final ZipFile zip; + private final FileEntry root; + + public JarMount( File jarFile, String subPath ) throws IOException + { + // Cleanup any old mounts. It's unlikely that there will be any, but it's best to be safe. + cleanup(); + + if( !jarFile.exists() || jarFile.isDirectory() ) throw new FileNotFoundException( "Cannot find " + jarFile ); + + // Open the zip file + try + { + zip = new ZipFile( jarFile ); + } + catch( IOException e ) + { + throw new IOException( "Error loading zip file", e ); + } + + // Ensure the root entry exists. + if( zip.getEntry( subPath ) == null ) + { + zip.close(); + throw new FileNotFoundException( "Zip does not contain path" ); + } + + // We now create a weak reference to this mount. This is automatically added to the appropriate queue. + new MountReference( this ); + + // Read in all the entries + root = new FileEntry( "" ); + Enumeration zipEntries = zip.entries(); + while( zipEntries.hasMoreElements() ) + { + ZipEntry entry = zipEntries.nextElement(); + + String entryPath = entry.getName(); + if( !entryPath.startsWith( subPath ) ) continue; + + String localPath = FileSystem.toLocal( entryPath, subPath ); + create( entry, localPath ); + } + } + + private FileEntry get( String path ) + { + FileEntry lastEntry = root; + int lastIndex = 0; + + while( lastEntry != null && lastIndex < path.length() ) + { + int nextIndex = path.indexOf( '/', lastIndex ); + if( nextIndex < 0 ) nextIndex = path.length(); + + lastEntry = lastEntry.children == null ? null : lastEntry.children.get( path.substring( lastIndex, nextIndex ) ); + lastIndex = nextIndex + 1; + } + + return lastEntry; + } + + private void create( ZipEntry entry, String localPath ) + { + FileEntry lastEntry = root; + + int lastIndex = 0; + while( lastIndex < localPath.length() ) + { + int nextIndex = localPath.indexOf( '/', lastIndex ); + if( nextIndex < 0 ) nextIndex = localPath.length(); + + String part = localPath.substring( lastIndex, nextIndex ); + if( lastEntry.children == null ) lastEntry.children = new HashMap<>( 0 ); + + FileEntry nextEntry = lastEntry.children.get( part ); + if( nextEntry == null || !nextEntry.isDirectory() ) + { + lastEntry.children.put( part, nextEntry = new FileEntry( part ) ); + } + + lastEntry = nextEntry; + lastIndex = nextIndex + 1; + } + + lastEntry.setup( entry ); + } + + @Override + public boolean exists( @Nonnull String path ) + { + return get( path ) != null; + } + + @Override + public boolean isDirectory( @Nonnull String path ) + { + FileEntry file = get( path ); + return file != null && file.isDirectory(); + } + + @Override + public void list( @Nonnull String path, @Nonnull List contents ) throws IOException + { + FileEntry file = get( path ); + if( file == null || !file.isDirectory() ) throw new IOException( "/" + path + ": Not a directory" ); + + file.list( contents ); + } + + @Override + public long getSize( @Nonnull String path ) throws IOException + { + FileEntry file = get( path ); + if( file != null ) return file.size; + throw new IOException( "/" + path + ": No such file" ); + } + + @Nonnull + @Override + @Deprecated + public InputStream openForRead( @Nonnull String path ) throws IOException + { + return Channels.newInputStream( openChannelForRead( path ) ); + } + + @Nonnull + @Override + public ReadableByteChannel openChannelForRead( @Nonnull String path ) throws IOException + { + FileEntry file = get( path ); + if( file != null && !file.isDirectory() ) + { + byte[] contents = CONTENTS_CACHE.getIfPresent( file ); + if( contents != null ) return new ArrayByteChannel( contents ); + + try + { + ZipEntry entry = zip.getEntry( file.path ); + if( entry != null ) + { + try( InputStream stream = zip.getInputStream( entry ) ) + { + if( stream.available() > MAX_CACHED_SIZE ) return Channels.newChannel( stream ); + + contents = ByteStreams.toByteArray( stream ); + CONTENTS_CACHE.put( file, contents ); + return new ArrayByteChannel( contents ); + } + } + } + catch( IOException e ) + { + // Treat errors as non-existence of file + } + } + + throw new IOException( "/" + path + ": No such file" ); + } + + private static class FileEntry + { + final String name; + + String path; + long size; + Map children; + + FileEntry( String name ) + { + this.name = name; + } + + void setup( ZipEntry entry ) + { + path = entry.getName(); + size = entry.getSize(); + if( children == null && entry.isDirectory() ) children = new HashMap<>( 0 ); + } + + boolean isDirectory() + { + return children != null; + } + + void list( List contents ) + { + if( children != null ) contents.addAll( children.keySet() ); + } + } + + private static class MountReference extends WeakReference + { + final ZipFile file; + + MountReference( JarMount file ) + { + super( file, MOUNT_QUEUE ); + this.file = file.zip; + } + } + + private static void cleanup() + { + Reference next; + while( (next = MOUNT_QUEUE.poll()) != null ) IoUtil.closeQuietly( ((MountReference) next).file ); + } +} diff --git a/remappedSrc/dan200/computercraft/core/filesystem/ResourceMount.java b/remappedSrc/dan200/computercraft/core/filesystem/ResourceMount.java new file mode 100644 index 000000000..801b3aca7 --- /dev/null +++ b/remappedSrc/dan200/computercraft/core/filesystem/ResourceMount.java @@ -0,0 +1,279 @@ +/* + * 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.filesystem; + +import com.google.common.cache.Cache; +import com.google.common.cache.CacheBuilder; +import com.google.common.io.ByteStreams; +import dan200.computercraft.api.filesystem.IMount; +import dan200.computercraft.core.apis.handles.ArrayByteChannel; +import dan200.computercraft.core.filesystem.ResourceMount.Listener; +import net.minecraft.resource.ReloadableResourceManager; +import net.minecraft.resource.Resource; +import net.minecraft.resource.ResourceManager; +import net.minecraft.resource.ResourceReloadListener; +import net.minecraft.util.Identifier; +import net.minecraft.util.profiler.Profiler; + +import javax.annotation.Nonnull; +import javax.annotation.Nullable; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.io.InputStream; +import java.nio.channels.Channels; +import java.nio.channels.ReadableByteChannel; +import java.util.*; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.Executor; +import java.util.concurrent.TimeUnit; + +public class ResourceMount implements IMount +{ + /** + * Only cache files smaller than 1MiB. + */ + private static final int MAX_CACHED_SIZE = 1 << 20; + + /** + * Limit the entire cache to 64MiB. + */ + private static final int MAX_CACHE_SIZE = 64 << 20; + + private static final byte[] TEMP_BUFFER = new byte[8192]; + + /** + * We maintain a cache of the contents of all files in the mount. This allows us to allow + * seeking within ROM files, and reduces the amount we need to access disk for computer startup. + */ + private static final Cache CONTENTS_CACHE = CacheBuilder.newBuilder() + .concurrencyLevel( 4 ) + .expireAfterAccess( 60, TimeUnit.SECONDS ) + .maximumWeight( MAX_CACHE_SIZE ) + .weakKeys() + .weigher( ( k, v ) -> v.length ) + .build(); + + private final String namespace; + private final String subPath; + private final ReloadableResourceManager manager; + + @Nullable + private FileEntry root; + + public ResourceMount( String namespace, String subPath, ReloadableResourceManager manager ) + { + this.namespace = namespace; + this.subPath = subPath; + this.manager = manager; + + Listener.INSTANCE.add( manager, this ); + if( root == null ) load(); + } + + private void load() + { + boolean hasAny = false; + FileEntry newRoot = new FileEntry( new Identifier( namespace, subPath ) ); + for( Identifier file : manager.findResources( subPath, s -> true ) ) + { + if( !file.getNamespace().equals( namespace ) ) continue; + + String localPath = FileSystem.toLocal( file.getPath(), subPath ); + create( newRoot, localPath ); + hasAny = true; + } + + root = hasAny ? newRoot : null; + } + + private FileEntry get( String path ) + { + FileEntry lastEntry = root; + int lastIndex = 0; + + while( lastEntry != null && lastIndex < path.length() ) + { + int nextIndex = path.indexOf( '/', lastIndex ); + if( nextIndex < 0 ) nextIndex = path.length(); + + lastEntry = lastEntry.children == null ? null : lastEntry.children.get( path.substring( lastIndex, nextIndex ) ); + lastIndex = nextIndex + 1; + } + + return lastEntry; + } + + private void create( FileEntry lastEntry, String path ) + { + int lastIndex = 0; + while( lastIndex < path.length() ) + { + int nextIndex = path.indexOf( '/', lastIndex ); + if( nextIndex < 0 ) nextIndex = path.length(); + + String part = path.substring( lastIndex, nextIndex ); + if( lastEntry.children == null ) lastEntry.children = new HashMap<>(); + + FileEntry nextEntry = lastEntry.children.get( part ); + if( nextEntry == null ) + { + lastEntry.children.put( part, nextEntry = new FileEntry( new Identifier( namespace, subPath + "/" + path ) ) ); + } + + lastEntry = nextEntry; + lastIndex = nextIndex + 1; + } + } + + @Override + public boolean exists( @Nonnull String path ) + { + return get( path ) != null; + } + + @Override + public boolean isDirectory( @Nonnull String path ) + { + FileEntry file = get( path ); + return file != null && file.isDirectory(); + } + + @Override + public void list( @Nonnull String path, @Nonnull List contents ) throws IOException + { + FileEntry file = get( path ); + if( file == null || !file.isDirectory() ) throw new IOException( "/" + path + ": Not a directory" ); + + file.list( contents ); + } + + @Override + public long getSize( @Nonnull String path ) throws IOException + { + FileEntry file = get( path ); + if( file != null ) + { + if( file.size != -1 ) return file.size; + if( file.isDirectory() ) return file.size = 0; + + byte[] contents = CONTENTS_CACHE.getIfPresent( file ); + if( contents != null ) return file.size = contents.length; + + try + { + Resource resource = manager.getResource( file.identifier ); + InputStream s = resource.getInputStream(); + int total = 0, read = 0; + do + { + total += read; + read = s.read( TEMP_BUFFER ); + } while( read > 0 ); + + return file.size = total; + } + catch( IOException e ) + { + return file.size = 0; + } + } + + throw new IOException( "/" + path + ": No such file" ); + } + + @Nonnull + @Override + @Deprecated + public InputStream openForRead( @Nonnull String path ) throws IOException + { + return Channels.newInputStream( openChannelForRead( path ) ); + } + + @Nonnull + @Override + public ReadableByteChannel openChannelForRead( @Nonnull String path ) throws IOException + { + FileEntry file = get( path ); + if( file != null && !file.isDirectory() ) + { + byte[] contents = CONTENTS_CACHE.getIfPresent( file ); + if( contents != null ) return new ArrayByteChannel( contents ); + + try( InputStream stream = manager.getResource( file.identifier ).getInputStream() ) + { + if( stream.available() > MAX_CACHED_SIZE ) return Channels.newChannel( stream ); + + contents = ByteStreams.toByteArray( stream ); + CONTENTS_CACHE.put( file, contents ); + return new ArrayByteChannel( contents ); + } + catch( FileNotFoundException ignored ) + { + } + } + + throw new IOException( "/" + path + ": No such file" ); + } + + private static class FileEntry + { + final Identifier identifier; + Map children; + long size = -1; + + FileEntry( Identifier identifier ) + { + this.identifier = identifier; + } + + boolean isDirectory() + { + return children != null; + } + + void list( List contents ) + { + if( children != null ) contents.addAll( children.keySet() ); + } + } + + /** + * A {@link ResourceReloadListener} which reloads any associated mounts. + * + * While people should really be keeping a permanent reference to this, some people construct it every + * method call, so let's make this as small as possible. + */ + static class Listener implements ResourceReloadListener + { + private static final Listener INSTANCE = new Listener(); + + private final Set mounts = Collections.newSetFromMap( new WeakHashMap<>() ); + private final Set managers = Collections.newSetFromMap( new WeakHashMap<>() ); + + @Override + public CompletableFuture reload( Synchronizer synchronizer, ResourceManager resourceManager, Profiler profiler, Profiler profiler1, Executor executor, Executor executor1 ) + { + return CompletableFuture.runAsync( () -> { + profiler.push( "Mount reloading" ); + try + { + for( ResourceMount mount : mounts ) mount.load(); + } + finally + { + profiler.pop(); + } + }, executor ); + } + + synchronized void add( ReloadableResourceManager manager, ResourceMount mount ) + { + if( managers.add( manager ) ) manager.registerListener( this ); + mounts.add( mount ); + } + } +} diff --git a/remappedSrc/dan200/computercraft/core/filesystem/SubMount.java b/remappedSrc/dan200/computercraft/core/filesystem/SubMount.java new file mode 100644 index 000000000..5b4c9c482 --- /dev/null +++ b/remappedSrc/dan200/computercraft/core/filesystem/SubMount.java @@ -0,0 +1,80 @@ +/* + * 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.filesystem; + +import dan200.computercraft.api.filesystem.IMount; + +import javax.annotation.Nonnull; +import java.io.IOException; +import java.io.InputStream; +import java.nio.channels.ReadableByteChannel; +import java.util.List; + +public class SubMount implements IMount +{ + private IMount m_parent; + private String m_subPath; + + public SubMount( IMount parent, String subPath ) + { + m_parent = parent; + m_subPath = subPath; + } + + // IMount implementation + + @Override + public boolean exists( @Nonnull String path ) throws IOException + { + return m_parent.exists( getFullPath( path ) ); + } + + @Override + public boolean isDirectory( @Nonnull String path ) throws IOException + { + return m_parent.isDirectory( getFullPath( path ) ); + } + + @Override + public void list( @Nonnull String path, @Nonnull List contents ) throws IOException + { + m_parent.list( getFullPath( path ), contents ); + } + + @Override + public long getSize( @Nonnull String path ) throws IOException + { + return m_parent.getSize( getFullPath( path ) ); + } + + @Nonnull + @Override + @Deprecated + public InputStream openForRead( @Nonnull String path ) throws IOException + { + return m_parent.openForRead( getFullPath( path ) ); + } + + @Nonnull + @Override + public ReadableByteChannel openChannelForRead( @Nonnull String path ) throws IOException + { + return m_parent.openChannelForRead( getFullPath( path ) ); + } + + private String getFullPath( String path ) + { + if( path.isEmpty() ) + { + return m_subPath; + } + else + { + return m_subPath + "/" + path; + } + } +} diff --git a/remappedSrc/dan200/computercraft/core/lua/CobaltLuaMachine.java b/remappedSrc/dan200/computercraft/core/lua/CobaltLuaMachine.java new file mode 100644 index 000000000..55a986e16 --- /dev/null +++ b/remappedSrc/dan200/computercraft/core/lua/CobaltLuaMachine.java @@ -0,0 +1,618 @@ +/* + * 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.lua; + +import dan200.computercraft.ComputerCraft; +import dan200.computercraft.api.lua.*; +import dan200.computercraft.core.computer.Computer; +import dan200.computercraft.core.computer.MainThread; +import dan200.computercraft.core.computer.TimeoutState; +import dan200.computercraft.core.tracking.Tracking; +import dan200.computercraft.core.tracking.TrackingField; +import dan200.computercraft.shared.util.ThreadUtils; +import org.squiddev.cobalt.*; +import org.squiddev.cobalt.compiler.CompileException; +import org.squiddev.cobalt.compiler.LoadState; +import org.squiddev.cobalt.debug.DebugFrame; +import org.squiddev.cobalt.debug.DebugHandler; +import org.squiddev.cobalt.debug.DebugState; +import org.squiddev.cobalt.function.LuaFunction; +import org.squiddev.cobalt.function.VarArgFunction; +import org.squiddev.cobalt.lib.*; +import org.squiddev.cobalt.lib.platform.VoidResourceManipulator; + +import javax.annotation.Nonnull; +import java.io.InputStream; +import java.util.Arrays; +import java.util.HashMap; +import java.util.IdentityHashMap; +import java.util.Map; +import java.util.concurrent.SynchronousQueue; +import java.util.concurrent.ThreadPoolExecutor; +import java.util.concurrent.TimeUnit; + +import static org.squiddev.cobalt.ValueFactory.valueOf; +import static org.squiddev.cobalt.ValueFactory.varargsOf; +import static org.squiddev.cobalt.debug.DebugFrame.FLAG_HOOKED; +import static org.squiddev.cobalt.debug.DebugFrame.FLAG_HOOKYIELD; + +public class CobaltLuaMachine implements ILuaMachine +{ + private static final ThreadPoolExecutor COROUTINES = new ThreadPoolExecutor( + 0, Integer.MAX_VALUE, + 5L, TimeUnit.MINUTES, + new SynchronousQueue<>(), + ThreadUtils.factory( "Coroutine" ) + ); + + private final Computer m_computer; + private final TimeoutState timeout; + private final TimeoutDebugHandler debug; + private final ILuaContext context = new CobaltLuaContext(); + + private LuaState m_state; + private LuaTable m_globals; + + private LuaThread m_mainRoutine = null; + private String m_eventFilter = null; + + public CobaltLuaMachine( Computer computer, TimeoutState timeout ) + { + m_computer = computer; + this.timeout = timeout; + debug = new TimeoutDebugHandler(); + + // Create an environment to run in + LuaState state = m_state = LuaState.builder() + .resourceManipulator( new VoidResourceManipulator() ) + .debug( debug ) + .coroutineExecutor( command -> { + Tracking.addValue( m_computer, TrackingField.COROUTINES_CREATED, 1 ); + COROUTINES.execute( () -> { + try + { + command.run(); + } + finally + { + Tracking.addValue( m_computer, TrackingField.COROUTINES_DISPOSED, 1 ); + } + } ); + } ) + .build(); + + m_globals = new LuaTable(); + state.setupThread( m_globals ); + + // Add basic libraries + m_globals.load( state, new BaseLib() ); + m_globals.load( state, new TableLib() ); + m_globals.load( state, new StringLib() ); + m_globals.load( state, new MathLib() ); + m_globals.load( state, new CoroutineLib() ); + m_globals.load( state, new Bit32Lib() ); + if( ComputerCraft.debug_enable ) m_globals.load( state, new DebugLib() ); + + // Remove globals we don't want to expose + m_globals.rawset( "collectgarbage", Constants.NIL ); + m_globals.rawset( "dofile", Constants.NIL ); + m_globals.rawset( "loadfile", Constants.NIL ); + m_globals.rawset( "print", Constants.NIL ); + + // Add version globals + m_globals.rawset( "_VERSION", valueOf( "Lua 5.1" ) ); + m_globals.rawset( "_HOST", valueOf( computer.getAPIEnvironment().getComputerEnvironment().getHostString() ) ); + m_globals.rawset( "_CC_DEFAULT_SETTINGS", valueOf( ComputerCraft.default_computer_settings ) ); + if( ComputerCraft.disable_lua51_features ) + { + m_globals.rawset( "_CC_DISABLE_LUA51_FEATURES", Constants.TRUE ); + } + } + + @Override + public void addAPI( @Nonnull ILuaAPI api ) + { + // Add the methods of an API to the global table + LuaTable table = wrapLuaObject( api ); + String[] names = api.getNames(); + for( String name : names ) + { + m_globals.rawset( name, table ); + } + } + + @Override + public MachineResult loadBios( @Nonnull InputStream bios ) + { + // Begin executing a file (ie, the bios) + if( m_mainRoutine != null ) return MachineResult.OK; + + try + { + LuaFunction value = LoadState.load( m_state, bios, "@bios.lua", m_globals ); + m_mainRoutine = new LuaThread( m_state, value, m_globals ); + return MachineResult.OK; + } + catch( CompileException e ) + { + close(); + return MachineResult.error( e ); + } + catch( Exception e ) + { + ComputerCraft.log.warn( "Could not load bios.lua", e ); + close(); + return MachineResult.GENERIC_ERROR; + } + } + + @Override + public MachineResult handleEvent( String eventName, Object[] arguments ) + { + if( m_mainRoutine == null ) return MachineResult.OK; + + if( m_eventFilter != null && eventName != null && !eventName.equals( m_eventFilter ) && !eventName.equals( "terminate" ) ) + { + return MachineResult.OK; + } + + // If the soft abort has been cleared then we can reset our flag. + timeout.refresh(); + if( !timeout.isSoftAborted() ) debug.thrownSoftAbort = false; + + try + { + Varargs resumeArgs = Constants.NONE; + if( eventName != null ) + { + resumeArgs = varargsOf( valueOf( eventName ), toValues( arguments ) ); + } + + // Resume the current thread, or the main one when first starting off. + LuaThread thread = m_state.getCurrentThread(); + if( thread == null || thread == m_state.getMainThread() ) thread = m_mainRoutine; + + Varargs results = LuaThread.run( thread, resumeArgs ); + if( timeout.isHardAborted() ) throw HardAbortError.INSTANCE; + if( results == null ) return MachineResult.PAUSE; + + LuaValue filter = results.first(); + m_eventFilter = filter.isString() ? filter.toString() : null; + + if( m_mainRoutine.getStatus().equals( "dead" ) ) + { + close(); + return MachineResult.GENERIC_ERROR; + } + else + { + return MachineResult.OK; + } + } + catch( HardAbortError | InterruptedException e ) + { + close(); + return MachineResult.TIMEOUT; + } + catch( LuaError e ) + { + close(); + ComputerCraft.log.warn( "Top level coroutine errored", e ); + return MachineResult.error( e ); + } + } + + @Override + public void close() + { + LuaState state = m_state; + if( state == null ) return; + + state.abandon(); + m_mainRoutine = null; + m_state = null; + m_globals = null; + } + + private LuaTable wrapLuaObject( ILuaObject object ) + { + LuaTable table = new LuaTable(); + String[] methods = object.getMethodNames(); + for( int i = 0; i < methods.length; i++ ) + { + if( methods[i] != null ) + { + final int method = i; + final ILuaObject apiObject = object; + final String methodName = methods[i]; + table.rawset( methodName, new VarArgFunction() + { + @Override + public Varargs invoke( final LuaState state, Varargs args ) throws LuaError + { + Object[] arguments = toObjects( args, 1 ); + Object[] results; + try + { + results = apiObject.callMethod( context, method, arguments ); + } + catch( InterruptedException e ) + { + throw new InterruptedError( e ); + } + catch( LuaException e ) + { + throw new LuaError( e.getMessage(), e.getLevel() ); + } + catch( Throwable t ) + { + if( ComputerCraft.logPeripheralErrors ) + { + ComputerCraft.log.error( "Error calling " + methodName + " on " + apiObject, t ); + } + throw new LuaError( "Java Exception Thrown: " + t, 0 ); + } + return toValues( results ); + } + } ); + } + } + return table; + } + + private LuaValue toValue( Object object, Map values ) + { + if( object == null ) + { + return Constants.NIL; + } + else if( object instanceof Number ) + { + double d = ((Number) object).doubleValue(); + return valueOf( d ); + } + else if( object instanceof Boolean ) + { + return valueOf( (Boolean) object ); + } + else if( object instanceof String ) + { + String s = object.toString(); + return valueOf( s ); + } + else if( object instanceof byte[] ) + { + byte[] b = (byte[]) object; + return valueOf( Arrays.copyOf( b, b.length ) ); + } + else if( object instanceof Map ) + { + // Table: + // Start remembering stuff + if( values == null ) + { + values = new IdentityHashMap<>(); + } + else if( values.containsKey( object ) ) + { + return values.get( object ); + } + LuaTable table = new LuaTable(); + values.put( object, table ); + + // Convert all keys + for( Map.Entry pair : ((Map) object).entrySet() ) + { + LuaValue key = toValue( pair.getKey(), values ); + LuaValue value = toValue( pair.getValue(), values ); + if( !key.isNil() && !value.isNil() ) + { + table.rawset( key, value ); + } + } + return table; + } + else if( object instanceof ILuaObject ) + { + return wrapLuaObject( (ILuaObject) object ); + } + else + { + return Constants.NIL; + } + } + + private Varargs toValues( Object[] objects ) + { + if( objects == null || objects.length == 0 ) + { + return Constants.NONE; + } + + LuaValue[] values = new LuaValue[objects.length]; + for( int i = 0; i < values.length; i++ ) + { + Object object = objects[i]; + values[i] = toValue( object, null ); + } + return varargsOf( values ); + } + + private static Object toObject( LuaValue value, Map objects ) + { + switch( value.type() ) + { + case Constants.TNIL: + case Constants.TNONE: + return null; + case Constants.TINT: + case Constants.TNUMBER: + return value.toDouble(); + case Constants.TBOOLEAN: + return value.toBoolean(); + case Constants.TSTRING: + return value.toString(); + case Constants.TTABLE: + { + // Table: + // Start remembering stuff + if( objects == null ) + { + objects = new IdentityHashMap<>(); + } + else if( objects.containsKey( value ) ) + { + return objects.get( value ); + } + Map table = new HashMap<>(); + objects.put( value, table ); + + LuaTable luaTable = (LuaTable) value; + + // Convert all keys + LuaValue k = Constants.NIL; + while( true ) + { + Varargs keyValue; + try + { + keyValue = luaTable.next( k ); + } + catch( LuaError luaError ) + { + break; + } + k = keyValue.first(); + if( k.isNil() ) + { + break; + } + + LuaValue v = keyValue.arg( 2 ); + Object keyObject = toObject( k, objects ); + Object valueObject = toObject( v, objects ); + if( keyObject != null && valueObject != null ) + { + table.put( keyObject, valueObject ); + } + } + return table; + } + default: + return null; + } + } + + private static Object[] toObjects( Varargs values, int startIdx ) + { + int count = values.count(); + Object[] objects = new Object[count - startIdx + 1]; + for( int n = startIdx; n <= count; n++ ) + { + int i = n - startIdx; + LuaValue value = values.arg( n ); + objects[i] = toObject( value, null ); + } + return objects; + } + + /** + * A {@link DebugHandler} which observes the {@link TimeoutState} and responds accordingly. + */ + private class TimeoutDebugHandler extends DebugHandler + { + private final TimeoutState timeout; + private int count = 0; + boolean thrownSoftAbort; + + private boolean isPaused; + private int oldFlags; + private boolean oldInHook; + + TimeoutDebugHandler() + { + timeout = CobaltLuaMachine.this.timeout; + } + + @Override + public void onInstruction( DebugState ds, DebugFrame di, int pc ) throws LuaError, UnwindThrowable + { + di.pc = pc; + + if( isPaused ) resetPaused( ds, di ); + + // We check our current pause/abort state every 128 instructions. + if( (count = (count + 1) & 127) == 0 ) + { + // If we've been hard aborted or closed then abort. + if( timeout.isHardAborted() || m_state == null ) throw HardAbortError.INSTANCE; + + timeout.refresh(); + if( timeout.isPaused() ) + { + // Preserve the current state + isPaused = true; + oldInHook = ds.inhook; + oldFlags = di.flags; + + // Suspend the state. This will probably throw, but we need to handle the case where it won't. + di.flags |= FLAG_HOOKYIELD | FLAG_HOOKED; + LuaThread.suspend( ds.getLuaState() ); + resetPaused( ds, di ); + } + + handleSoftAbort(); + } + + super.onInstruction( ds, di, pc ); + } + + @Override + public void poll() throws LuaError + { + // If we've been hard aborted or closed then abort. + LuaState state = m_state; + if( timeout.isHardAborted() || state == null ) throw HardAbortError.INSTANCE; + + timeout.refresh(); + if( timeout.isPaused() ) LuaThread.suspendBlocking( state ); + handleSoftAbort(); + } + + private void resetPaused( DebugState ds, DebugFrame di ) + { + // Restore the previous paused state + isPaused = false; + ds.inhook = oldInHook; + di.flags = oldFlags; + } + + private void handleSoftAbort() throws LuaError + { + // If we already thrown our soft abort error then don't do it again. + if( !timeout.isSoftAborted() || thrownSoftAbort ) return; + + thrownSoftAbort = true; + throw new LuaError( TimeoutState.ABORT_MESSAGE ); + } + } + + private class CobaltLuaContext implements ILuaContext + { + @Nonnull + @Override + public Object[] yield( Object[] yieldArgs ) throws InterruptedException + { + try + { + LuaState state = m_state; + if( state == null ) throw new InterruptedException(); + Varargs results = LuaThread.yieldBlocking( state, toValues( yieldArgs ) ); + return toObjects( results, 1 ); + } + catch( LuaError e ) + { + throw new IllegalStateException( e.getMessage() ); + } + } + + @Override + public long issueMainThreadTask( @Nonnull final ILuaTask task ) throws LuaException + { + // Issue command + final long taskID = MainThread.getUniqueTaskID(); + final Runnable iTask = () -> { + try + { + Object[] results = task.execute(); + if( results != null ) + { + Object[] eventArguments = new Object[results.length + 2]; + eventArguments[0] = taskID; + eventArguments[1] = true; + System.arraycopy( results, 0, eventArguments, 2, results.length ); + m_computer.queueEvent( "task_complete", eventArguments ); + } + else + { + m_computer.queueEvent( "task_complete", new Object[] { taskID, true } ); + } + } + catch( LuaException e ) + { + m_computer.queueEvent( "task_complete", new Object[] { taskID, false, e.getMessage() } ); + } + catch( Throwable t ) + { + if( ComputerCraft.logPeripheralErrors ) ComputerCraft.log.error( "Error running task", t ); + m_computer.queueEvent( "task_complete", new Object[] { + taskID, false, "Java Exception Thrown: " + t + } ); + } + }; + if( m_computer.queueMainThread( iTask ) ) + { + return taskID; + } + else + { + throw new LuaException( "Task limit exceeded" ); + } + } + + @Override + public Object[] executeMainThreadTask( @Nonnull final ILuaTask task ) throws LuaException, InterruptedException + { + // Issue task + final long taskID = issueMainThreadTask( task ); + + // Wait for response + while( true ) + { + Object[] response = pullEvent( "task_complete" ); + if( response.length >= 3 && response[1] instanceof Number && response[2] instanceof Boolean ) + { + if( ((Number) response[1]).intValue() == taskID ) + { + Object[] returnValues = new Object[response.length - 3]; + if( (Boolean) response[2] ) + { + // Extract the return values from the event and return them + System.arraycopy( response, 3, returnValues, 0, returnValues.length ); + return returnValues; + } + else + { + // Extract the error message from the event and raise it + if( response.length >= 4 && response[3] instanceof String ) + { + throw new LuaException( (String) response[3] ); + } + else + { + throw new LuaException(); + } + } + } + } + } + + } + } + + private static final class HardAbortError extends Error + { + private static final long serialVersionUID = 7954092008586367501L; + + static final HardAbortError INSTANCE = new HardAbortError(); + + private HardAbortError() + { + super( "Hard Abort", null, true, false ); + } + } +} diff --git a/remappedSrc/dan200/computercraft/core/lua/ILuaMachine.java b/remappedSrc/dan200/computercraft/core/lua/ILuaMachine.java new file mode 100644 index 000000000..700eb34aa --- /dev/null +++ b/remappedSrc/dan200/computercraft/core/lua/ILuaMachine.java @@ -0,0 +1,67 @@ +/* + * 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.lua; + +import dan200.computercraft.api.lua.ILuaAPI; +import dan200.computercraft.api.lua.ILuaObject; + +import javax.annotation.Nonnull; +import javax.annotation.Nullable; +import java.io.InputStream; + +/** + * Represents a machine which will execute Lua code. Technically this API is flexible enough to support many languages, + * but you'd need a way to provide alternative ROMs, BIOSes, etc... + * + * There should only be one concrete implementation at any one time, which is currently {@link CobaltLuaMachine}. If + * external mod authors are interested in registering their own machines, we can look into how we can provide some + * mechanism for registering these. + * + * This should provide implementations of {@link dan200.computercraft.api.lua.ILuaContext}, and the ability to convert + * {@link ILuaObject}s into something the VM understands, as well as handling method calls. + */ +public interface ILuaMachine +{ + /** + * Inject an API into the global environment of this machine. This should construct an object, as it would for any + * {@link ILuaObject} and set it to all names in {@link ILuaAPI#getNames()}. + * + * Called before {@link #loadBios(InputStream)}. + * + * @param api The API to register. + */ + void addAPI( @Nonnull ILuaAPI api ); + + /** + * Create a function from the provided program, and set it up to run when {@link #handleEvent(String, Object[])} is + * called. + * + * This should destroy the machine if it failed to load the bios. + * + * @param bios The stream containing the boot program. + * @return The result of loading this machine. Will either be OK, or the error message when loading the bios. + */ + MachineResult loadBios( @Nonnull InputStream bios ); + + /** + * Resume the machine, either starting or resuming the coroutine. + * + * This should destroy the machine if it failed to execute successfully. + * + * @param eventName The name of the event. This is {@code null} when first starting the machine. Note, this may + * do nothing if it does not match the event filter. + * @param arguments The arguments for this event. + * @return The result of loading this machine. Will either be OK, or the error message that occurred when + * executing. + */ + MachineResult handleEvent( @Nullable String eventName, @Nullable Object[] arguments ); + + /** + * Close the Lua machine, aborting any running functions and deleting the internal state. + */ + void close(); +} diff --git a/remappedSrc/dan200/computercraft/core/lua/MachineResult.java b/remappedSrc/dan200/computercraft/core/lua/MachineResult.java new file mode 100644 index 000000000..26a4608d7 --- /dev/null +++ b/remappedSrc/dan200/computercraft/core/lua/MachineResult.java @@ -0,0 +1,81 @@ +/* + * 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.lua; + +import dan200.computercraft.core.computer.TimeoutState; + +import javax.annotation.Nonnull; +import javax.annotation.Nullable; +import java.io.InputStream; + +/** + * The result of executing an action on a machine. + * + * Errors should halt the machine and display the error to the user. + * + * @see ILuaMachine#loadBios(InputStream) + * @see ILuaMachine#handleEvent(String, Object[]) + */ +public final class MachineResult +{ + /** + * A successful complete execution. + */ + public static final MachineResult OK = new MachineResult( false, false, null ); + + /** + * A successful paused execution. + */ + public static final MachineResult PAUSE = new MachineResult( false, true, null ); + + /** + * An execution which timed out. + */ + public static final MachineResult TIMEOUT = new MachineResult( true, false, TimeoutState.ABORT_MESSAGE ); + + /** + * An error with no user-friendly error message + */ + public static final MachineResult GENERIC_ERROR = new MachineResult( true, false, null ); + + private final boolean error; + private final boolean pause; + private final String message; + + private MachineResult( boolean error, boolean pause, String message ) + { + this.pause = pause; + this.message = message; + this.error = error; + } + + public static MachineResult error( @Nonnull String error ) + { + return new MachineResult( true, false, error ); + } + + public static MachineResult error( @Nonnull Exception error ) + { + return new MachineResult( true, false, error.getMessage() ); + } + + public boolean isError() + { + return error; + } + + public boolean isPause() + { + return pause; + } + + @Nullable + public String getMessage() + { + return message; + } +} diff --git a/remappedSrc/dan200/computercraft/core/terminal/Terminal.java b/remappedSrc/dan200/computercraft/core/terminal/Terminal.java new file mode 100644 index 000000000..46bf64491 --- /dev/null +++ b/remappedSrc/dan200/computercraft/core/terminal/Terminal.java @@ -0,0 +1,389 @@ +/* + * 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.terminal; + +import dan200.computercraft.shared.util.Palette; +import net.minecraft.nbt.CompoundTag; + +public class Terminal +{ + private static final String base16 = "0123456789abcdef"; + + private int m_cursorX; + private int m_cursorY; + private boolean m_cursorBlink; + private int m_cursorColour; + private int m_cursorBackgroundColour; + + private int m_width; + private int m_height; + + private TextBuffer[] m_text; + private TextBuffer[] m_textColour; + private TextBuffer[] m_backgroundColour; + + private final Palette m_palette; + + private boolean m_changed; + private final Runnable onChanged; + + public Terminal( int width, int height ) + { + this( width, height, null ); + } + + public Terminal( int width, int height, Runnable changedCallback ) + { + m_width = width; + m_height = height; + onChanged = changedCallback; + + m_cursorColour = 0; + m_cursorBackgroundColour = 15; + + m_text = new TextBuffer[m_height]; + m_textColour = new TextBuffer[m_height]; + m_backgroundColour = new TextBuffer[m_height]; + for( int i = 0; i < m_height; i++ ) + { + m_text[i] = new TextBuffer( ' ', m_width ); + m_textColour[i] = new TextBuffer( base16.charAt( m_cursorColour ), m_width ); + m_backgroundColour[i] = new TextBuffer( base16.charAt( m_cursorBackgroundColour ), m_width ); + } + + m_cursorX = 0; + m_cursorY = 0; + m_cursorBlink = false; + + m_changed = false; + + m_palette = new Palette(); + } + + public synchronized void reset() + { + m_cursorColour = 0; + m_cursorBackgroundColour = 15; + m_cursorX = 0; + m_cursorY = 0; + m_cursorBlink = false; + clear(); + setChanged(); + m_palette.resetColours(); + } + + public int getWidth() + { + return m_width; + } + + public int getHeight() + { + return m_height; + } + + public synchronized void resize( int width, int height ) + { + if( width == m_width && height == m_height ) + { + return; + } + + int oldHeight = m_height; + int oldWidth = m_width; + TextBuffer[] oldText = m_text; + TextBuffer[] oldTextColour = m_textColour; + TextBuffer[] oldBackgroundColour = m_backgroundColour; + + m_width = width; + m_height = height; + + m_text = new TextBuffer[m_height]; + m_textColour = new TextBuffer[m_height]; + m_backgroundColour = new TextBuffer[m_height]; + for( int i = 0; i < m_height; i++ ) + { + if( i >= oldHeight ) + { + m_text[i] = new TextBuffer( ' ', m_width ); + m_textColour[i] = new TextBuffer( base16.charAt( m_cursorColour ), m_width ); + m_backgroundColour[i] = new TextBuffer( base16.charAt( m_cursorBackgroundColour ), m_width ); + } + else if( m_width == oldWidth ) + { + m_text[i] = oldText[i]; + m_textColour[i] = oldTextColour[i]; + m_backgroundColour[i] = oldBackgroundColour[i]; + } + else + { + m_text[i] = new TextBuffer( ' ', m_width ); + m_textColour[i] = new TextBuffer( base16.charAt( m_cursorColour ), m_width ); + m_backgroundColour[i] = new TextBuffer( base16.charAt( m_cursorBackgroundColour ), m_width ); + m_text[i].write( oldText[i] ); + m_textColour[i].write( oldTextColour[i] ); + m_backgroundColour[i].write( oldBackgroundColour[i] ); + } + } + setChanged(); + } + + public void setCursorPos( int x, int y ) + { + if( m_cursorX != x || m_cursorY != y ) + { + m_cursorX = x; + m_cursorY = y; + setChanged(); + } + } + + public void setCursorBlink( boolean blink ) + { + if( m_cursorBlink != blink ) + { + m_cursorBlink = blink; + setChanged(); + } + } + + public void setTextColour( int colour ) + { + if( m_cursorColour != colour ) + { + m_cursorColour = colour; + setChanged(); + } + } + + public void setBackgroundColour( int colour ) + { + if( m_cursorBackgroundColour != colour ) + { + m_cursorBackgroundColour = colour; + setChanged(); + } + } + + public int getCursorX() + { + return m_cursorX; + } + + public int getCursorY() + { + return m_cursorY; + } + + public boolean getCursorBlink() + { + return m_cursorBlink; + } + + public int getTextColour() + { + return m_cursorColour; + } + + public int getBackgroundColour() + { + return m_cursorBackgroundColour; + } + + public Palette getPalette() + { + return m_palette; + } + + public synchronized void blit( String text, String textColour, String backgroundColour ) + { + int x = m_cursorX; + int y = m_cursorY; + if( y >= 0 && y < m_height ) + { + m_text[y].write( text, x ); + m_textColour[y].write( textColour, x ); + m_backgroundColour[y].write( backgroundColour, x ); + setChanged(); + } + } + + public synchronized void write( String text ) + { + int x = m_cursorX; + int y = m_cursorY; + if( y >= 0 && y < m_height ) + { + m_text[y].write( text, x ); + m_textColour[y].fill( base16.charAt( m_cursorColour ), x, x + text.length() ); + m_backgroundColour[y].fill( base16.charAt( m_cursorBackgroundColour ), x, x + text.length() ); + setChanged(); + } + } + + public synchronized void scroll( int yDiff ) + { + if( yDiff != 0 ) + { + TextBuffer[] newText = new TextBuffer[m_height]; + TextBuffer[] newTextColour = new TextBuffer[m_height]; + TextBuffer[] newBackgroundColour = new TextBuffer[m_height]; + for( int y = 0; y < m_height; y++ ) + { + int oldY = y + yDiff; + if( oldY >= 0 && oldY < m_height ) + { + newText[y] = m_text[oldY]; + newTextColour[y] = m_textColour[oldY]; + newBackgroundColour[y] = m_backgroundColour[oldY]; + } + else + { + newText[y] = new TextBuffer( ' ', m_width ); + newTextColour[y] = new TextBuffer( base16.charAt( m_cursorColour ), m_width ); + newBackgroundColour[y] = new TextBuffer( base16.charAt( m_cursorBackgroundColour ), m_width ); + } + } + m_text = newText; + m_textColour = newTextColour; + m_backgroundColour = newBackgroundColour; + setChanged(); + } + } + + public synchronized void clear() + { + for( int y = 0; y < m_height; y++ ) + { + m_text[y].fill( ' ' ); + m_textColour[y].fill( base16.charAt( m_cursorColour ) ); + m_backgroundColour[y].fill( base16.charAt( m_cursorBackgroundColour ) ); + } + setChanged(); + } + + public synchronized void clearLine() + { + int y = m_cursorY; + if( y >= 0 && y < m_height ) + { + m_text[y].fill( ' ' ); + m_textColour[y].fill( base16.charAt( m_cursorColour ) ); + m_backgroundColour[y].fill( base16.charAt( m_cursorBackgroundColour ) ); + setChanged(); + } + } + + public synchronized TextBuffer getLine( int y ) + { + if( y >= 0 && y < m_height ) + { + return m_text[y]; + } + return null; + } + + public synchronized void setLine( int y, String text, String textColour, String backgroundColour ) + { + m_text[y].write( text ); + m_textColour[y].write( textColour ); + m_backgroundColour[y].write( backgroundColour ); + setChanged(); + } + + public synchronized TextBuffer getTextColourLine( int y ) + { + if( y >= 0 && y < m_height ) + { + return m_textColour[y]; + } + return null; + } + + public synchronized TextBuffer getBackgroundColourLine( int y ) + { + if( y >= 0 && y < m_height ) + { + return m_backgroundColour[y]; + } + return null; + } + + /** + * @deprecated All {@code *Changed()} methods are deprecated: one should pass in a callback + * instead. + */ + @Deprecated + public final boolean getChanged() + { + return m_changed; + } + + public final void setChanged() + { + m_changed = true; + if( onChanged != null ) onChanged.run(); + } + + public final void clearChanged() + { + m_changed = false; + } + + public synchronized CompoundTag writeToNBT( CompoundTag nbt ) + { + nbt.putInt( "term_cursorX", m_cursorX ); + nbt.putInt( "term_cursorY", m_cursorY ); + nbt.putBoolean( "term_cursorBlink", m_cursorBlink ); + nbt.putInt( "term_textColour", m_cursorColour ); + nbt.putInt( "term_bgColour", m_cursorBackgroundColour ); + for( int n = 0; n < m_height; n++ ) + { + nbt.putString( "term_text_" + n, m_text[n].toString() ); + nbt.putString( "term_textColour_" + n, m_textColour[n].toString() ); + nbt.putString( "term_textBgColour_" + n, m_backgroundColour[n].toString() ); + } + if( m_palette != null ) + { + m_palette.writeToNBT( nbt ); + } + return nbt; + } + + public synchronized void readFromNBT( CompoundTag nbt ) + { + m_cursorX = nbt.getInt( "term_cursorX" ); + m_cursorY = nbt.getInt( "term_cursorY" ); + m_cursorBlink = nbt.getBoolean( "term_cursorBlink" ); + m_cursorColour = nbt.getInt( "term_textColour" ); + m_cursorBackgroundColour = nbt.getInt( "term_bgColour" ); + + for( int n = 0; n < m_height; n++ ) + { + m_text[n].fill( ' ' ); + if( nbt.contains( "term_text_" + n ) ) + { + m_text[n].write( nbt.getString( "term_text_" + n ) ); + } + m_textColour[n].fill( base16.charAt( m_cursorColour ) ); + if( nbt.contains( "term_textColour_" + n ) ) + { + m_textColour[n].write( nbt.getString( "term_textColour_" + n ) ); + } + m_backgroundColour[n].fill( base16.charAt( m_cursorBackgroundColour ) ); + if( nbt.contains( "term_textBgColour_" + n ) ) + { + m_backgroundColour[n].write( nbt.getString( "term_textBgColour_" + n ) ); + } + } + if( m_palette != null ) + { + m_palette.readFromNBT( nbt ); + } + setChanged(); + } +} diff --git a/remappedSrc/dan200/computercraft/core/terminal/TextBuffer.java b/remappedSrc/dan200/computercraft/core/terminal/TextBuffer.java new file mode 100644 index 000000000..b7e0aa5ab --- /dev/null +++ b/remappedSrc/dan200/computercraft/core/terminal/TextBuffer.java @@ -0,0 +1,208 @@ +/* + * 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.terminal; + +public class TextBuffer +{ + public char[] m_text; + + public TextBuffer( char c, int length ) + { + m_text = new char[length]; + for( int i = 0; i < length; i++ ) + { + m_text[i] = c; + } + } + + public TextBuffer( String text ) + { + this( text, 1 ); + } + + public TextBuffer( String text, int repetitions ) + { + int textLength = text.length(); + m_text = new char[textLength * repetitions]; + for( int i = 0; i < repetitions; i++ ) + { + for( int j = 0; j < textLength; j++ ) + { + m_text[j + i * textLength] = text.charAt( j ); + } + } + } + + public TextBuffer( TextBuffer text ) + { + this( text, 1 ); + } + + public TextBuffer( TextBuffer text, int repetitions ) + { + int textLength = text.length(); + m_text = new char[textLength * repetitions]; + for( int i = 0; i < repetitions; i++ ) + { + for( int j = 0; j < textLength; j++ ) + { + m_text[j + i * textLength] = text.charAt( j ); + } + } + } + + public int length() + { + return m_text.length; + } + + public String read() + { + return read( 0, m_text.length ); + } + + public String read( int start ) + { + return read( start, m_text.length ); + } + + public String read( int start, int end ) + { + start = Math.max( start, 0 ); + end = Math.min( end, m_text.length ); + int textLength = Math.max( end - start, 0 ); + return new String( m_text, start, textLength ); + } + + public void write( String text ) + { + write( text, 0, text.length() ); + } + + public void write( String text, int start ) + { + write( text, start, start + text.length() ); + } + + public void write( String text, int start, int end ) + { + int pos = start; + start = Math.max( start, 0 ); + end = Math.min( end, pos + text.length() ); + end = Math.min( end, m_text.length ); + for( int i = start; i < end; i++ ) + { + m_text[i] = text.charAt( i - pos ); + } + } + + public void write( TextBuffer text ) + { + write( text, 0, text.length() ); + } + + public void write( TextBuffer text, int start ) + { + write( text, start, start + text.length() ); + } + + public void write( TextBuffer text, int start, int end ) + { + int pos = start; + start = Math.max( start, 0 ); + end = Math.min( end, pos + text.length() ); + end = Math.min( end, m_text.length ); + for( int i = start; i < end; i++ ) + { + m_text[i] = text.charAt( i - pos ); + } + } + + public void fill( char c ) + { + fill( c, 0, m_text.length ); + } + + public void fill( char c, int start ) + { + fill( c, start, m_text.length ); + } + + public void fill( char c, int start, int end ) + { + start = Math.max( start, 0 ); + end = Math.min( end, m_text.length ); + for( int i = start; i < end; i++ ) + { + m_text[i] = c; + } + } + + public void fill( String text ) + { + fill( text, 0, m_text.length ); + } + + public void fill( String text, int start ) + { + fill( text, start, m_text.length ); + } + + public void fill( String text, int start, int end ) + { + int pos = start; + start = Math.max( start, 0 ); + end = Math.min( end, m_text.length ); + + int textLength = text.length(); + for( int i = start; i < end; i++ ) + { + m_text[i] = text.charAt( (i - pos) % textLength ); + } + } + + public void fill( TextBuffer text ) + { + fill( text, 0, m_text.length ); + } + + public void fill( TextBuffer text, int start ) + { + fill( text, start, m_text.length ); + } + + public void fill( TextBuffer text, int start, int end ) + { + int pos = start; + start = Math.max( start, 0 ); + end = Math.min( end, m_text.length ); + + int textLength = text.length(); + for( int i = start; i < end; i++ ) + { + m_text[i] = text.charAt( (i - pos) % textLength ); + } + } + + public char charAt( int i ) + { + return m_text[i]; + } + + public void setChar( int i, char c ) + { + if( i >= 0 && i < m_text.length ) + { + m_text[i] = c; + } + } + + public String toString() + { + return new String( m_text ); + } +} diff --git a/remappedSrc/dan200/computercraft/core/tracking/ComputerTracker.java b/remappedSrc/dan200/computercraft/core/tracking/ComputerTracker.java new file mode 100644 index 000000000..9ee52cb38 --- /dev/null +++ b/remappedSrc/dan200/computercraft/core/tracking/ComputerTracker.java @@ -0,0 +1,123 @@ +/* + * 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.tracking; + +import dan200.computercraft.core.computer.Computer; +import it.unimi.dsi.fastutil.objects.Object2LongOpenHashMap; + +import javax.annotation.Nullable; +import java.lang.ref.WeakReference; + +public class ComputerTracker +{ + private final WeakReference computer; + private final int computerId; + + private long tasks; + private long totalTime; + private long maxTime; + + private long serverCount; + private long serverTime; + + private final Object2LongOpenHashMap fields; + + public ComputerTracker( Computer computer ) + { + this.computer = new WeakReference<>( computer ); + computerId = computer.getID(); + fields = new Object2LongOpenHashMap<>(); + } + + ComputerTracker( ComputerTracker timings ) + { + computer = timings.computer; + computerId = timings.computerId; + + tasks = timings.tasks; + totalTime = timings.totalTime; + maxTime = timings.maxTime; + + serverCount = timings.serverCount; + serverTime = timings.serverTime; + + fields = new Object2LongOpenHashMap<>( timings.fields ); + } + + @Nullable + public Computer getComputer() + { + return computer.get(); + } + + public int getComputerId() + { + return computerId; + } + + public long getTasks() + { + return tasks; + } + + public long getTotalTime() + { + return totalTime; + } + + public long getMaxTime() + { + return maxTime; + } + + public long getAverage() + { + return totalTime / tasks; + } + + void addTaskTiming( long time ) + { + tasks++; + totalTime += time; + if( time > maxTime ) maxTime = time; + } + + void addMainTiming( long time ) + { + serverCount++; + serverTime += time; + } + + void addValue( TrackingField field, long change ) + { + synchronized( fields ) + { + fields.addTo( field, change ); + } + } + + public long get( TrackingField field ) + { + if( field == TrackingField.TASKS ) return tasks; + if( field == TrackingField.MAX_TIME ) return maxTime; + if( field == TrackingField.TOTAL_TIME ) return totalTime; + if( field == TrackingField.AVERAGE_TIME ) return tasks == 0 ? 0 : totalTime / tasks; + + if( field == TrackingField.SERVER_COUNT ) return serverCount; + if( field == TrackingField.SERVER_TIME ) return serverTime; + + synchronized( fields ) + { + return fields.getLong( field ); + } + } + + public String getFormatted( TrackingField field ) + { + return field.format( get( field ) ); + } +} diff --git a/remappedSrc/dan200/computercraft/core/tracking/Tracker.java b/remappedSrc/dan200/computercraft/core/tracking/Tracker.java new file mode 100644 index 000000000..3d9957235 --- /dev/null +++ b/remappedSrc/dan200/computercraft/core/tracking/Tracker.java @@ -0,0 +1,48 @@ +/* + * 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.tracking; + +import dan200.computercraft.core.computer.Computer; + +public interface Tracker +{ + /** + * Report how long a task executed on the computer thread took. + * + * Computer thread tasks include events or a computer being turned on/off. + * + * @param computer The computer processing this task + * @param time The time taken for this task. + */ + default void addTaskTiming( Computer computer, long time ) + { + } + + /** + * Report how long a task executed on the server thread took. + * + * Server tasks include actions performed by peripherals. + * + * @param computer The computer processing this task + * @param time The time taken for this task. + */ + default void addServerTiming( Computer computer, long time ) + { + } + + /** + * Increment an arbitrary field by some value. Implementations may track how often this is called + * as well as the change, to compute some level of "average". + * + * @param computer The computer to increment + * @param field The field to increment. + * @param change The amount to increment said field by. + */ + default void addValue( Computer computer, TrackingField field, long change ) + { + } +} diff --git a/remappedSrc/dan200/computercraft/core/tracking/Tracking.java b/remappedSrc/dan200/computercraft/core/tracking/Tracking.java new file mode 100644 index 000000000..6df9de33e --- /dev/null +++ b/remappedSrc/dan200/computercraft/core/tracking/Tracking.java @@ -0,0 +1,88 @@ +/* + * 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.tracking; + +import dan200.computercraft.core.computer.Computer; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.UUID; +import java.util.concurrent.atomic.AtomicInteger; + +public final class Tracking +{ + static final AtomicInteger tracking = new AtomicInteger( 0 ); + + private static final Object lock = new Object(); + private static final HashMap contexts = new HashMap<>(); + private static final List trackers = new ArrayList<>(); + + private Tracking() {} + + public static TrackingContext getContext( UUID uuid ) + { + synchronized( lock ) + { + TrackingContext context = contexts.get( uuid ); + if( context == null ) contexts.put( uuid, context = new TrackingContext() ); + return context; + } + } + + public static void add( Tracker tracker ) + { + synchronized( lock ) + { + trackers.add( tracker ); + tracking.incrementAndGet(); + } + } + + public static void addTaskTiming( Computer computer, long time ) + { + if( tracking.get() == 0 ) return; + + synchronized( contexts ) + { + for( TrackingContext context : contexts.values() ) context.addTaskTiming( computer, time ); + for( Tracker tracker : trackers ) tracker.addTaskTiming( computer, time ); + } + } + + public static void addServerTiming( Computer computer, long time ) + { + if( tracking.get() == 0 ) return; + + synchronized( contexts ) + { + for( TrackingContext context : contexts.values() ) context.addServerTiming( computer, time ); + for( Tracker tracker : trackers ) tracker.addServerTiming( computer, time ); + } + } + + public static void addValue( Computer computer, TrackingField field, long change ) + { + if( tracking.get() == 0 ) return; + + synchronized( lock ) + { + for( TrackingContext context : contexts.values() ) context.addValue( computer, field, change ); + for( Tracker tracker : trackers ) tracker.addValue( computer, field, change ); + } + } + + public static void reset() + { + synchronized( lock ) + { + contexts.clear(); + trackers.clear(); + tracking.set( 0 ); + } + } +} diff --git a/remappedSrc/dan200/computercraft/core/tracking/TrackingContext.java b/remappedSrc/dan200/computercraft/core/tracking/TrackingContext.java new file mode 100644 index 000000000..d9d6d2c05 --- /dev/null +++ b/remappedSrc/dan200/computercraft/core/tracking/TrackingContext.java @@ -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.core.tracking; + +import com.google.common.collect.MapMaker; +import dan200.computercraft.core.computer.Computer; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +/** + * Tracks timing information about computers, including how long they ran for + * and the number of events they handled. + * + * Note that this will track computers which have been deleted (hence + * the presence of {@link #timingLookup} and {@link #timings} + */ +public class TrackingContext implements Tracker +{ + private boolean tracking = false; + + private final List timings = new ArrayList<>(); + private final Map timingLookup = new MapMaker().weakKeys().makeMap(); + + public synchronized void start() + { + if( !tracking ) Tracking.tracking.incrementAndGet(); + tracking = true; + + timings.clear(); + timingLookup.clear(); + } + + public synchronized boolean stop() + { + if( !tracking ) return false; + + Tracking.tracking.decrementAndGet(); + tracking = false; + timingLookup.clear(); + return true; + } + + public synchronized List getImmutableTimings() + { + ArrayList timings = new ArrayList<>( this.timings.size() ); + for( ComputerTracker timing : this.timings ) timings.add( new ComputerTracker( timing ) ); + return timings; + } + + public synchronized List getTimings() + { + return new ArrayList<>( timings ); + } + + @Override + public void addTaskTiming( Computer computer, long time ) + { + if( !tracking ) return; + + synchronized( this ) + { + ComputerTracker computerTimings = timingLookup.get( computer ); + if( computerTimings == null ) + { + computerTimings = new ComputerTracker( computer ); + timingLookup.put( computer, computerTimings ); + timings.add( computerTimings ); + } + + computerTimings.addTaskTiming( time ); + } + } + + @Override + public void addServerTiming( Computer computer, long time ) + { + if( !tracking ) return; + + synchronized( this ) + { + ComputerTracker computerTimings = timingLookup.get( computer ); + if( computerTimings == null ) + { + computerTimings = new ComputerTracker( computer ); + timingLookup.put( computer, computerTimings ); + timings.add( computerTimings ); + } + + computerTimings.addMainTiming( time ); + } + } + + @Override + public void addValue( Computer computer, TrackingField field, long change ) + { + if( !tracking ) return; + + synchronized( this ) + { + ComputerTracker computerTimings = timingLookup.get( computer ); + if( computerTimings == null ) + { + computerTimings = new ComputerTracker( computer ); + timingLookup.put( computer, computerTimings ); + timings.add( computerTimings ); + } + + computerTimings.addValue( field, change ); + } + } +} diff --git a/remappedSrc/dan200/computercraft/core/tracking/TrackingField.java b/remappedSrc/dan200/computercraft/core/tracking/TrackingField.java new file mode 100644 index 000000000..9b4fec82a --- /dev/null +++ b/remappedSrc/dan200/computercraft/core/tracking/TrackingField.java @@ -0,0 +1,97 @@ +/* + * 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.tracking; + +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; +import java.util.function.LongFunction; + +public final class TrackingField +{ + private static final Map fields = new HashMap<>(); + + public static final TrackingField TASKS = TrackingField.of( "tasks", x -> String.format( "%4d", x ) ); + public static final TrackingField TOTAL_TIME = TrackingField.of( "total", x -> String.format( "%7.1fms", x / 1e6 ) ); + public static final TrackingField AVERAGE_TIME = TrackingField.of( "average", x -> String.format( "%4.1fms", x / 1e6 ) ); + public static final TrackingField MAX_TIME = TrackingField.of( "max", x -> String.format( "%5.1fms", x / 1e6 ) ); + + public static final TrackingField SERVER_COUNT = TrackingField.of( "server_count", x -> String.format( "%4d", x ) ); + public static final TrackingField SERVER_TIME = TrackingField.of( "server_time", x -> String.format( "%7.1fms", x / 1e6 ) ); + + public static final TrackingField PERIPHERAL_OPS = TrackingField.of( "peripheral", TrackingField::formatDefault ); + public static final TrackingField FS_OPS = TrackingField.of( "fs", TrackingField::formatDefault ); + public static final TrackingField TURTLE_OPS = TrackingField.of( "turtle", TrackingField::formatDefault ); + + public static final TrackingField HTTP_REQUESTS = TrackingField.of( "http", TrackingField::formatDefault ); + public static final TrackingField HTTP_UPLOAD = TrackingField.of( "http_upload", TrackingField::formatBytes ); + public static final TrackingField HTTP_DOWNLOAD = TrackingField.of( "http_download", TrackingField::formatBytes ); + + public static final TrackingField WEBSOCKET_INCOMING = TrackingField.of( "websocket_incoming", TrackingField::formatBytes ); + public static final TrackingField WEBSOCKET_OUTGOING = TrackingField.of( "websocket_outgoing", TrackingField::formatBytes ); + + public static final TrackingField COROUTINES_CREATED = TrackingField.of( "coroutines_created", x -> String.format( "%4d", x ) ); + public static final TrackingField COROUTINES_DISPOSED = TrackingField.of( "coroutines_dead", x -> String.format( "%4d", x ) ); + + private final String id; + private final String translationKey; + private final LongFunction format; + + public String id() + { + return id; + } + + public String translationKey() + { + return translationKey; + } + + private TrackingField( String id, LongFunction format ) + { + this.id = id; + translationKey = "tracking_field.computercraft." + id + ".name"; + this.format = format; + } + + public String format( long value ) + { + return format.apply( value ); + } + + public static TrackingField of( String id, LongFunction format ) + { + TrackingField field = new TrackingField( id, format ); + fields.put( id, field ); + return field; + } + + public static Map fields() + { + return Collections.unmodifiableMap( fields ); + } + + private static String formatDefault( long value ) + { + return String.format( "%6d", value ); + } + + /** + * So technically a kibibyte, but let's not argue here. + */ + private static final int KILOBYTE_SIZE = 1024; + + private static final String SI_PREFIXES = "KMGT"; + + private static String formatBytes( long bytes ) + { + if( bytes < 1024 ) return String.format( "%10d B", bytes ); + int exp = (int) (Math.log( bytes ) / Math.log( KILOBYTE_SIZE )); + if( exp > SI_PREFIXES.length() ) exp = SI_PREFIXES.length(); + return String.format( "%10.1f %siB", bytes / Math.pow( KILOBYTE_SIZE, exp ), SI_PREFIXES.charAt( exp - 1 ) ); + } +} diff --git a/remappedSrc/dan200/computercraft/shared/BundledRedstone.java b/remappedSrc/dan200/computercraft/shared/BundledRedstone.java new file mode 100644 index 000000000..dda37859e --- /dev/null +++ b/remappedSrc/dan200/computercraft/shared/BundledRedstone.java @@ -0,0 +1,68 @@ +/* + * 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.shared; + +import dan200.computercraft.ComputerCraft; +import dan200.computercraft.api.redstone.IBundledRedstoneProvider; +import dan200.computercraft.shared.common.DefaultBundledRedstoneProvider; +import net.minecraft.util.math.BlockPos; +import net.minecraft.util.math.Direction; +import net.minecraft.world.World; + +import javax.annotation.Nonnull; +import java.util.LinkedHashSet; +import java.util.Objects; +import java.util.Set; + +public final class BundledRedstone +{ + private static final Set providers = new LinkedHashSet<>(); + + private BundledRedstone() {} + + public static void register( @Nonnull IBundledRedstoneProvider provider ) + { + Objects.requireNonNull( provider, "provider cannot be null" ); + providers.add( provider ); + } + + public static int getDefaultOutput( @Nonnull World world, @Nonnull BlockPos pos, @Nonnull Direction side ) + { + return World.isValid( pos ) ? DefaultBundledRedstoneProvider.getDefaultBundledRedstoneOutput( world, pos, side ) : -1; + } + + private static int getUnmaskedOutput( World world, BlockPos pos, Direction side ) + { + if( !World.isValid( pos ) ) return -1; + + // Try the providers in order: + int combinedSignal = -1; + for( IBundledRedstoneProvider bundledRedstoneProvider : providers ) + { + try + { + int signal = bundledRedstoneProvider.getBundledRedstoneOutput( world, pos, side ); + if( signal >= 0 ) + { + combinedSignal = combinedSignal < 0 ? signal & 0xffff : combinedSignal | (signal & 0xffff); + } + } + catch( Exception e ) + { + ComputerCraft.log.error( "Bundled redstone provider " + bundledRedstoneProvider + " errored.", e ); + } + } + + return combinedSignal; + } + + public static int getOutput( World world, BlockPos pos, Direction side ) + { + int signal = getUnmaskedOutput( world, pos, side ); + return signal >= 0 ? signal : 0; + } +} diff --git a/remappedSrc/dan200/computercraft/shared/MediaProviders.java b/remappedSrc/dan200/computercraft/shared/MediaProviders.java new file mode 100644 index 000000000..a0addc710 --- /dev/null +++ b/remappedSrc/dan200/computercraft/shared/MediaProviders.java @@ -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.shared; + +import dan200.computercraft.ComputerCraft; +import dan200.computercraft.api.media.IMedia; +import dan200.computercraft.api.media.IMediaProvider; +import net.minecraft.item.ItemStack; + +import javax.annotation.Nonnull; +import java.util.LinkedHashSet; +import java.util.Objects; +import java.util.Set; + +public final class MediaProviders +{ + private static final Set providers = new LinkedHashSet<>(); + + private MediaProviders() {} + + public static synchronized void register( @Nonnull IMediaProvider provider ) + { + Objects.requireNonNull( provider, "provider cannot be null" ); + providers.add( provider ); + } + + public static IMedia get( @Nonnull ItemStack stack ) + { + if( stack.isEmpty() ) return null; + + // Try the handlers in order: + for( IMediaProvider mediaProvider : providers ) + { + try + { + IMedia media = mediaProvider.getMedia( stack ); + if( media != null ) return media; + } + catch( Exception e ) + { + // mod misbehaved, ignore it + ComputerCraft.log.error( "Media provider " + mediaProvider + " errored.", e ); + } + } + return null; + } +} diff --git a/remappedSrc/dan200/computercraft/shared/Peripherals.java b/remappedSrc/dan200/computercraft/shared/Peripherals.java new file mode 100644 index 000000000..4a37c6f52 --- /dev/null +++ b/remappedSrc/dan200/computercraft/shared/Peripherals.java @@ -0,0 +1,57 @@ +/* + * 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.shared; + +import dan200.computercraft.ComputerCraft; +import dan200.computercraft.api.peripheral.IPeripheral; +import dan200.computercraft.api.peripheral.IPeripheralProvider; +import net.minecraft.util.math.BlockPos; +import net.minecraft.util.math.Direction; +import net.minecraft.world.World; + +import javax.annotation.Nonnull; +import java.util.Collection; +import java.util.LinkedHashSet; +import java.util.Objects; + +public final class Peripherals +{ + private static final Collection providers = new LinkedHashSet<>(); + + private Peripherals() {} + + public static synchronized void register( @Nonnull IPeripheralProvider provider ) + { + Objects.requireNonNull( provider, "provider cannot be null" ); + providers.add( provider ); + } + + public static IPeripheral getPeripheral( World world, BlockPos pos, Direction side ) + { + return World.isValid( pos ) && !world.isClient ? getPeripheralAt( world, pos, side ) : null; + } + + private static IPeripheral getPeripheralAt( World world, BlockPos pos, Direction side ) + { + // Try the handlers in order: + for( IPeripheralProvider peripheralProvider : providers ) + { + try + { + IPeripheral peripheral = peripheralProvider.getPeripheral( world, pos, side ); + if( peripheral != null ) return peripheral; + } + catch( Exception e ) + { + ComputerCraft.log.error( "Peripheral provider " + peripheralProvider + " errored.", e ); + } + } + + return null; + } + +} diff --git a/remappedSrc/dan200/computercraft/shared/PocketUpgrades.java b/remappedSrc/dan200/computercraft/shared/PocketUpgrades.java new file mode 100644 index 000000000..1d8050cd0 --- /dev/null +++ b/remappedSrc/dan200/computercraft/shared/PocketUpgrades.java @@ -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.shared; + +import dan200.computercraft.ComputerCraft; +import dan200.computercraft.api.pocket.IPocketUpgrade; +import dan200.computercraft.shared.util.InventoryUtil; +import net.minecraft.item.ItemStack; + +import javax.annotation.Nonnull; +import java.util.*; + +public final class PocketUpgrades +{ + private static final Map upgrades = new HashMap<>(); + + private PocketUpgrades() {} + + public static synchronized void register( @Nonnull IPocketUpgrade upgrade ) + { + Objects.requireNonNull( upgrade, "upgrade cannot be null" ); + + String id = upgrade.getUpgradeID().toString(); + IPocketUpgrade existing = upgrades.get( id ); + if( existing != null ) + { + throw new IllegalStateException( "Error registering '" + upgrade.getUnlocalisedAdjective() + " pocket computer'. UpgradeID '" + id + "' is already registered by '" + existing.getUnlocalisedAdjective() + " pocket computer'" ); + } + + upgrades.put( id, upgrade ); + } + + public static IPocketUpgrade get( String id ) + { + // Fix a typo in the advanced modem upgrade's name. I'm sorry, I realise this is horrible. + if( id.equals( "computercraft:advanved_modem" ) ) id = "computercraft:advanced_modem"; + + return upgrades.get( id ); + } + + public static IPocketUpgrade get( @Nonnull ItemStack stack ) + { + if( stack.isEmpty() ) return null; + + for( IPocketUpgrade upgrade : upgrades.values() ) + { + ItemStack craftingStack = upgrade.getCraftingItem(); + if( !craftingStack.isEmpty() && InventoryUtil.areItemsSimilar( stack, craftingStack ) ) + { + return upgrade; + } + } + + return null; + } + + public static Iterable getVanillaUpgrades() + { + List vanilla = new ArrayList<>(); + vanilla.add( ComputerCraft.PocketUpgrades.wirelessModemNormal ); + vanilla.add( ComputerCraft.PocketUpgrades.wirelessModemAdvanced ); + vanilla.add( ComputerCraft.PocketUpgrades.speaker ); + return vanilla; + } + + public static Iterable getUpgrades() + { + return Collections.unmodifiableCollection( upgrades.values() ); + } +} diff --git a/remappedSrc/dan200/computercraft/shared/Registry.java b/remappedSrc/dan200/computercraft/shared/Registry.java new file mode 100644 index 000000000..40b987463 --- /dev/null +++ b/remappedSrc/dan200/computercraft/shared/Registry.java @@ -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.shared; + +import dan200.computercraft.ComputerCraft; +import dan200.computercraft.api.ComputerCraftAPI; +import dan200.computercraft.shared.common.ColourableRecipe; +import dan200.computercraft.shared.computer.blocks.BlockComputer; +import dan200.computercraft.shared.computer.blocks.TileCommandComputer; +import dan200.computercraft.shared.computer.blocks.TileComputer; +import dan200.computercraft.shared.computer.core.ComputerFamily; +import dan200.computercraft.shared.computer.items.ItemComputer; +import dan200.computercraft.shared.computer.recipe.ComputerUpgradeRecipe; +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.media.recipes.DiskRecipe; +import dan200.computercraft.shared.media.recipes.PrintoutRecipe; +import dan200.computercraft.shared.peripheral.diskdrive.BlockDiskDrive; +import dan200.computercraft.shared.peripheral.diskdrive.TileDiskDrive; +import dan200.computercraft.shared.peripheral.modem.wired.*; +import dan200.computercraft.shared.peripheral.modem.wireless.BlockWirelessModem; +import dan200.computercraft.shared.peripheral.modem.wireless.TileWirelessModem; +import dan200.computercraft.shared.peripheral.monitor.BlockMonitor; +import dan200.computercraft.shared.peripheral.monitor.TileMonitor; +import dan200.computercraft.shared.peripheral.printer.BlockPrinter; +import dan200.computercraft.shared.peripheral.printer.TilePrinter; +import dan200.computercraft.shared.peripheral.speaker.BlockSpeaker; +import dan200.computercraft.shared.peripheral.speaker.TileSpeaker; +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.pocket.recipes.PocketComputerUpgradeRecipe; +import dan200.computercraft.shared.turtle.blocks.BlockTurtle; +import dan200.computercraft.shared.turtle.blocks.TileTurtle; +import dan200.computercraft.shared.turtle.items.ItemTurtle; +import dan200.computercraft.shared.turtle.recipes.TurtleRecipe; +import dan200.computercraft.shared.turtle.recipes.TurtleUpgradeRecipe; +import dan200.computercraft.shared.turtle.upgrades.*; +import dan200.computercraft.shared.util.ImpostorRecipe; +import dan200.computercraft.shared.util.ImpostorShapelessRecipe; +import net.fabricmc.fabric.api.block.FabricBlockSettings; +import net.fabricmc.fabric.api.client.itemgroup.FabricItemGroupBuilder; +import net.minecraft.block.Block; +import net.minecraft.block.Material; +import net.minecraft.block.entity.BlockEntityType; +import net.minecraft.item.*; +import net.minecraft.recipe.RecipeSerializer; +import net.minecraft.util.Identifier; +import net.minecraft.util.registry.MutableRegistry; + +public final class Registry +{ + private static final ItemGroup mainItemGroup = FabricItemGroupBuilder + .create( new Identifier( ComputerCraft.MOD_ID, "main" ) ) + .icon( () -> new ItemStack( ComputerCraft.Items.computerNormal ) ) + .build(); + + private Registry() + { + } + + public static void registerBlocks( MutableRegistry registry ) + { + // Computers + ComputerCraft.Blocks.computerNormal = new BlockComputer( + FabricBlockSettings.of( Material.STONE ).hardness( 2.0f ).build(), + ComputerFamily.Normal, TileComputer.FACTORY_NORMAL + ); + + ComputerCraft.Blocks.computerAdvanced = new BlockComputer( + FabricBlockSettings.of( Material.STONE ).hardness( 2.0f ).build(), + ComputerFamily.Advanced, TileComputer.FACTORY_ADVANCED + ); + + ComputerCraft.Blocks.computerCommand = new BlockComputer( + FabricBlockSettings.of( Material.STONE ).strength( -1, 6000000.0F ).build(), + ComputerFamily.Command, TileCommandComputer.FACTORY + ); + + registry.add( new Identifier( ComputerCraft.MOD_ID, "computer_normal" ), ComputerCraft.Blocks.computerNormal ); + registry.add( new Identifier( ComputerCraft.MOD_ID, "computer_advanced" ), ComputerCraft.Blocks.computerAdvanced ); + registry.add( new Identifier( ComputerCraft.MOD_ID, "computer_command" ), ComputerCraft.Blocks.computerCommand ); + + // Turtles + ComputerCraft.Blocks.turtleNormal = new BlockTurtle( + FabricBlockSettings.of( Material.STONE ).hardness( 2.5f ).build(), + ComputerFamily.Normal, TileTurtle.FACTORY_NORMAL + ); + + ComputerCraft.Blocks.turtleAdvanced = new BlockTurtle( + FabricBlockSettings.of( Material.STONE ).hardness( 2.5f ).build(), + ComputerFamily.Advanced, TileTurtle.FACTORY_ADVANCED + ); + + registry.add( new Identifier( ComputerCraft.MOD_ID, "turtle_normal" ), ComputerCraft.Blocks.turtleNormal ); + registry.add( new Identifier( ComputerCraft.MOD_ID, "turtle_advanced" ), ComputerCraft.Blocks.turtleAdvanced ); + + // Peripherals + ComputerCraft.Blocks.speaker = new BlockSpeaker( + FabricBlockSettings.of( Material.STONE ).hardness( 2 ).build() + ); + + ComputerCraft.Blocks.diskDrive = new BlockDiskDrive( + FabricBlockSettings.of( Material.STONE ).hardness( 2 ).build() + ); + + ComputerCraft.Blocks.monitorNormal = new BlockMonitor( + FabricBlockSettings.of( Material.STONE ).hardness( 2 ).build(), + TileMonitor.FACTORY_NORMAL + ); + + ComputerCraft.Blocks.monitorAdvanced = new BlockMonitor( + FabricBlockSettings.of( Material.STONE ).hardness( 2 ).build(), + TileMonitor.FACTORY_ADVANCED + ); + + ComputerCraft.Blocks.printer = new BlockPrinter( + FabricBlockSettings.of( Material.STONE ).hardness( 2 ).build() + ); + + ComputerCraft.Blocks.wirelessModemNormal = new BlockWirelessModem( + FabricBlockSettings.of( Material.STONE ).hardness( 2 ).build(), + TileWirelessModem.FACTORY_NORMAL + ); + + ComputerCraft.Blocks.wirelessModemAdvanced = new BlockWirelessModem( + FabricBlockSettings.of( Material.STONE ).hardness( 2 ).build(), + TileWirelessModem.FACTORY_ADVANCED + ); + + ComputerCraft.Blocks.wiredModemFull = new BlockWiredModemFull( + FabricBlockSettings.of( Material.STONE ).hardness( 1.5f ).build() + ); + + ComputerCraft.Blocks.cable = new BlockCable( + FabricBlockSettings.of( Material.STONE ).hardness( 1.5f ).build() + ); + + registry.add( new Identifier( ComputerCraft.MOD_ID, "speaker" ), ComputerCraft.Blocks.speaker ); + registry.add( new Identifier( ComputerCraft.MOD_ID, "disk_drive" ), ComputerCraft.Blocks.diskDrive ); + registry.add( new Identifier( ComputerCraft.MOD_ID, "monitor_normal" ), ComputerCraft.Blocks.monitorNormal ); + registry.add( new Identifier( ComputerCraft.MOD_ID, "monitor_advanced" ), ComputerCraft.Blocks.monitorAdvanced ); + registry.add( new Identifier( ComputerCraft.MOD_ID, "printer" ), ComputerCraft.Blocks.printer ); + registry.add( new Identifier( ComputerCraft.MOD_ID, "wireless_modem_normal" ), ComputerCraft.Blocks.wirelessModemNormal ); + registry.add( new Identifier( ComputerCraft.MOD_ID, "wireless_modem_advanced" ), ComputerCraft.Blocks.wirelessModemAdvanced ); + registry.add( new Identifier( ComputerCraft.MOD_ID, "wired_modem_full" ), ComputerCraft.Blocks.wiredModemFull ); + registry.add( new Identifier( ComputerCraft.MOD_ID, "cable" ), ComputerCraft.Blocks.cable ); + } + + public static void registerTileEntities( MutableRegistry> registry ) + { + // Computers + registry.add( TileComputer.FACTORY_NORMAL.getId(), TileComputer.FACTORY_NORMAL ); + registry.add( TileComputer.FACTORY_ADVANCED.getId(), TileComputer.FACTORY_ADVANCED ); + registry.add( TileCommandComputer.FACTORY.getId(), TileCommandComputer.FACTORY ); + + // Turtles + registry.add( TileTurtle.FACTORY_NORMAL.getId(), TileTurtle.FACTORY_NORMAL ); + registry.add( TileTurtle.FACTORY_ADVANCED.getId(), TileTurtle.FACTORY_ADVANCED ); + + // Peripherals + registry.add( TileSpeaker.FACTORY.getId(), TileSpeaker.FACTORY ); + registry.add( TileDiskDrive.FACTORY.getId(), TileDiskDrive.FACTORY ); + registry.add( TilePrinter.FACTORY.getId(), TilePrinter.FACTORY ); + + registry.add( TileMonitor.FACTORY_NORMAL.getId(), TileMonitor.FACTORY_NORMAL ); + registry.add( TileMonitor.FACTORY_ADVANCED.getId(), TileMonitor.FACTORY_ADVANCED ); + + registry.add( TileWirelessModem.FACTORY_NORMAL.getId(), TileWirelessModem.FACTORY_NORMAL ); + registry.add( TileWirelessModem.FACTORY_ADVANCED.getId(), TileWirelessModem.FACTORY_ADVANCED ); + registry.add( TileCable.FACTORY.getId(), TileCable.FACTORY ); + registry.add( TileWiredModemFull.FACTORY.getId(), TileWiredModemFull.FACTORY ); + } + + private static void registerItemBlock( MutableRegistry registry, BlockItem item ) + { + registry.add( net.minecraft.util.registry.Registry.BLOCK.getId( item.getBlock() ), item ); + } + + private static Item.Settings defaultItem() + { + return new Item.Settings().group( mainItemGroup ); + } + + public static void registerItems( MutableRegistry registry ) + { + // Computer + ComputerCraft.Items.computerNormal = new ItemComputer( ComputerCraft.Blocks.computerNormal, defaultItem() ); + ComputerCraft.Items.computerAdvanced = new ItemComputer( ComputerCraft.Blocks.computerAdvanced, defaultItem() ); + ComputerCraft.Items.computerCommand = new ItemComputer( ComputerCraft.Blocks.computerCommand, defaultItem() ); + + registerItemBlock( registry, ComputerCraft.Items.computerNormal ); + registerItemBlock( registry, ComputerCraft.Items.computerAdvanced ); + registerItemBlock( registry, ComputerCraft.Items.computerCommand ); + + // Turtle + ComputerCraft.Items.turtleNormal = new ItemTurtle( ComputerCraft.Blocks.turtleNormal, defaultItem() ); + ComputerCraft.Items.turtleAdvanced = new ItemTurtle( ComputerCraft.Blocks.turtleAdvanced, defaultItem() ); + + registerItemBlock( registry, ComputerCraft.Items.turtleNormal ); + registerItemBlock( registry, ComputerCraft.Items.turtleAdvanced ); + + // Pocket computer + ComputerCraft.Items.pocketComputerNormal = new ItemPocketComputer( defaultItem().maxCount( 1 ), ComputerFamily.Normal ); + ComputerCraft.Items.pocketComputerAdvanced = new ItemPocketComputer( defaultItem().maxCount( 1 ), ComputerFamily.Advanced ); + + registry.add( new Identifier( ComputerCraft.MOD_ID, "pocket_computer_normal" ), ComputerCraft.Items.pocketComputerNormal ); + registry.add( new Identifier( ComputerCraft.MOD_ID, "pocket_computer_advanced" ), ComputerCraft.Items.pocketComputerAdvanced ); + + // Floppy disk + ComputerCraft.Items.disk = new ItemDisk( defaultItem().maxCount( 1 ) ); + ComputerCraft.Items.treasureDisk = new ItemTreasureDisk( defaultItem().maxCount( 1 ) ); + + registry.add( new Identifier( ComputerCraft.MOD_ID, "disk" ), ComputerCraft.Items.disk ); + registry.add( new Identifier( ComputerCraft.MOD_ID, "treasure_disk" ), ComputerCraft.Items.treasureDisk ); + + // Printouts + ComputerCraft.Items.printedPage = new ItemPrintout( defaultItem().maxCount( 1 ), ItemPrintout.Type.PAGE ); + ComputerCraft.Items.printedPages = new ItemPrintout( defaultItem().maxCount( 1 ), ItemPrintout.Type.PAGES ); + ComputerCraft.Items.printedBook = new ItemPrintout( defaultItem().maxCount( 1 ), ItemPrintout.Type.BOOK ); + + registry.add( new Identifier( ComputerCraft.MOD_ID, "printed_page" ), ComputerCraft.Items.printedPage ); + registry.add( new Identifier( ComputerCraft.MOD_ID, "printed_pages" ), ComputerCraft.Items.printedPages ); + registry.add( new Identifier( ComputerCraft.MOD_ID, "printed_book" ), ComputerCraft.Items.printedBook ); + + // Peripherals + registerItemBlock( registry, new BlockItem( ComputerCraft.Blocks.speaker, defaultItem() ) ); + registerItemBlock( registry, new BlockItem( ComputerCraft.Blocks.diskDrive, defaultItem() ) ); + registerItemBlock( registry, new BlockItem( ComputerCraft.Blocks.printer, defaultItem() ) ); + registerItemBlock( registry, new BlockItem( ComputerCraft.Blocks.monitorNormal, defaultItem() ) ); + registerItemBlock( registry, new BlockItem( ComputerCraft.Blocks.monitorAdvanced, defaultItem() ) ); + registerItemBlock( registry, new BlockItem( ComputerCraft.Blocks.wirelessModemNormal, defaultItem() ) ); + registerItemBlock( registry, new BlockItem( ComputerCraft.Blocks.wirelessModemAdvanced, defaultItem() ) ); + registerItemBlock( registry, new BlockItem( ComputerCraft.Blocks.wiredModemFull, defaultItem() ) ); + + ComputerCraft.Items.cable = new ItemBlockCable.Cable( ComputerCraft.Blocks.cable, defaultItem() ); + ComputerCraft.Items.wiredModem = new ItemBlockCable.WiredModem( ComputerCraft.Blocks.cable, defaultItem() ); + + registry.add( new Identifier( ComputerCraft.MOD_ID, "cable" ), ComputerCraft.Items.cable ); + registry.add( new Identifier( ComputerCraft.MOD_ID, "wired_modem" ), ComputerCraft.Items.wiredModem ); + + registerTurtleUpgrades(); + registerPocketUpgrades(); + } + + private static void registerTurtleUpgrades() + { + // Upgrades + ComputerCraft.TurtleUpgrades.wirelessModemNormal = new TurtleModem( false, new Identifier( ComputerCraft.MOD_ID, "wireless_modem_normal" ) ); + ComputerCraftAPI.registerTurtleUpgrade( ComputerCraft.TurtleUpgrades.wirelessModemNormal ); + + ComputerCraft.TurtleUpgrades.wirelessModemAdvanced = new TurtleModem( true, new Identifier( ComputerCraft.MOD_ID, "wireless_modem_advanced" ) ); + ComputerCraftAPI.registerTurtleUpgrade( ComputerCraft.TurtleUpgrades.wirelessModemAdvanced ); + + ComputerCraft.TurtleUpgrades.speaker = new TurtleSpeaker( new Identifier( ComputerCraft.MOD_ID, "speaker" ) ); + ComputerCraftAPI.registerTurtleUpgrade( ComputerCraft.TurtleUpgrades.speaker ); + + ComputerCraft.TurtleUpgrades.craftingTable = new TurtleCraftingTable( new Identifier( "minecraft", "crafting_table" ) ); + ComputerCraftAPI.registerTurtleUpgrade( ComputerCraft.TurtleUpgrades.craftingTable ); + + ComputerCraft.TurtleUpgrades.diamondSword = new TurtleSword( new Identifier( "minecraft", "diamond_sword" ), Items.DIAMOND_SWORD ); + ComputerCraftAPI.registerTurtleUpgrade( ComputerCraft.TurtleUpgrades.diamondSword ); + + ComputerCraft.TurtleUpgrades.diamondShovel = new TurtleShovel( new Identifier( "minecraft", "diamond_shovel" ), Items.DIAMOND_SHOVEL ); + ComputerCraftAPI.registerTurtleUpgrade( ComputerCraft.TurtleUpgrades.diamondShovel ); + + ComputerCraft.TurtleUpgrades.diamondPickaxe = new TurtleTool( new Identifier( "minecraft", "diamond_pickaxe" ), Items.DIAMOND_PICKAXE ); + ComputerCraftAPI.registerTurtleUpgrade( ComputerCraft.TurtleUpgrades.diamondPickaxe ); + + ComputerCraft.TurtleUpgrades.diamondAxe = new TurtleAxe( new Identifier( "minecraft", "diamond_axe" ), Items.DIAMOND_AXE ); + ComputerCraftAPI.registerTurtleUpgrade( ComputerCraft.TurtleUpgrades.diamondAxe ); + + ComputerCraft.TurtleUpgrades.diamondHoe = new TurtleHoe( new Identifier( "minecraft", "diamond_hoe" ), Items.DIAMOND_HOE ); + ComputerCraftAPI.registerTurtleUpgrade( ComputerCraft.TurtleUpgrades.diamondHoe ); + } + + private static void registerPocketUpgrades() + { + ComputerCraftAPI.registerPocketUpgrade( ComputerCraft.PocketUpgrades.wirelessModemNormal = new PocketModem( false ) ); + ComputerCraftAPI.registerPocketUpgrade( ComputerCraft.PocketUpgrades.wirelessModemAdvanced = new PocketModem( true ) ); + ComputerCraftAPI.registerPocketUpgrade( ComputerCraft.PocketUpgrades.speaker = new PocketSpeaker() ); + } + + public static void registerRecipes( MutableRegistry> registry ) + { + registry.add( new Identifier( ComputerCraft.MOD_ID, "colour" ), ColourableRecipe.SERIALIZER ); + registry.add( new Identifier( ComputerCraft.MOD_ID, "computer_upgrade" ), ComputerUpgradeRecipe.SERIALIZER ); + registry.add( new Identifier( ComputerCraft.MOD_ID, "pocket_computer_upgrade" ), PocketComputerUpgradeRecipe.SERIALIZER ); + registry.add( new Identifier( ComputerCraft.MOD_ID, "disk" ), DiskRecipe.SERIALIZER ); + registry.add( new Identifier( ComputerCraft.MOD_ID, "printout" ), PrintoutRecipe.SERIALIZER ); + registry.add( new Identifier( ComputerCraft.MOD_ID, "turtle" ), TurtleRecipe.SERIALIZER ); + registry.add( new Identifier( ComputerCraft.MOD_ID, "turtle_upgrade" ), TurtleUpgradeRecipe.SERIALIZER ); + registry.add( new Identifier( ComputerCraft.MOD_ID, "impostor_shaped" ), ImpostorRecipe.SERIALIZER ); + registry.add( new Identifier( ComputerCraft.MOD_ID, "impostor_shapeless" ), ImpostorShapelessRecipe.SERIALIZER ); + } +} diff --git a/remappedSrc/dan200/computercraft/shared/TurtlePermissions.java b/remappedSrc/dan200/computercraft/shared/TurtlePermissions.java new file mode 100644 index 000000000..4c2cc36d8 --- /dev/null +++ b/remappedSrc/dan200/computercraft/shared/TurtlePermissions.java @@ -0,0 +1,39 @@ +/* + * 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.shared; + +import com.google.common.eventbus.Subscribe; +import dan200.computercraft.ComputerCraft; +import dan200.computercraft.api.turtle.event.TurtleActionEvent; +import net.minecraft.entity.player.PlayerEntity; +import net.minecraft.server.MinecraftServer; +import net.minecraft.util.math.BlockPos; +import net.minecraft.world.World; + +public final class TurtlePermissions +{ + public static boolean isBlockEnterable( World world, BlockPos pos, PlayerEntity player ) + { + MinecraftServer server = world.getServer(); + return server == null || world.isClient || !server.isSpawnProtected( world, pos, player ); + } + + public static boolean isBlockEditable( World world, BlockPos pos, PlayerEntity player ) + { + MinecraftServer server = world.getServer(); + return server == null || world.isClient || !server.isSpawnProtected( world, pos, player ); + } + + @Subscribe + public void onTurtleAction( TurtleActionEvent event ) + { + if( ComputerCraft.turtleDisabledActions.contains( event.getAction() ) ) + { + event.setCanceled( true, "Action has been disabled" ); + } + } +} diff --git a/remappedSrc/dan200/computercraft/shared/TurtleUpgrades.java b/remappedSrc/dan200/computercraft/shared/TurtleUpgrades.java new file mode 100644 index 000000000..0bf0cb637 --- /dev/null +++ b/remappedSrc/dan200/computercraft/shared/TurtleUpgrades.java @@ -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.shared; + +import dan200.computercraft.ComputerCraft; +import dan200.computercraft.api.turtle.ITurtleUpgrade; +import dan200.computercraft.shared.computer.core.ComputerFamily; +import dan200.computercraft.shared.util.InventoryUtil; +import net.minecraft.item.ItemStack; + +import javax.annotation.Nonnull; +import java.util.*; + +public final class TurtleUpgrades +{ + private static final Map upgrades = new HashMap<>(); + + private TurtleUpgrades() {} + + public static void register( @Nonnull ITurtleUpgrade upgrade ) + { + Objects.requireNonNull( upgrade, "upgrade cannot be null" ); + + String id = upgrade.getUpgradeID().toString(); + ITurtleUpgrade existing = upgrades.get( id ); + if( existing != null ) + { + throw new IllegalStateException( "Error registering '" + upgrade.getUnlocalisedAdjective() + " Turle'. UpgradeID '" + id + "' is already registered by '" + existing.getUnlocalisedAdjective() + " Turtle'" ); + } + + upgrades.put( id, upgrade ); + } + + + public static ITurtleUpgrade get( String id ) + { + return upgrades.get( id ); + } + + public static ITurtleUpgrade get( @Nonnull ItemStack stack ) + { + if( stack.isEmpty() ) return null; + + for( ITurtleUpgrade upgrade : upgrades.values() ) + { + ItemStack craftingStack = upgrade.getCraftingItem(); + if( !craftingStack.isEmpty() && InventoryUtil.areItemsSimilar( stack, craftingStack ) ) + { + return upgrade; + } + } + + return null; + } + + public static Iterable getVanillaUpgrades() + { + List vanilla = new ArrayList<>(); + + + // ComputerCraft upgrades + vanilla.add( ComputerCraft.TurtleUpgrades.wirelessModemNormal ); + vanilla.add( ComputerCraft.TurtleUpgrades.wirelessModemAdvanced ); + vanilla.add( ComputerCraft.TurtleUpgrades.speaker ); + + // Vanilla Minecraft upgrades + vanilla.add( ComputerCraft.TurtleUpgrades.diamondPickaxe ); + vanilla.add( ComputerCraft.TurtleUpgrades.diamondAxe ); + vanilla.add( ComputerCraft.TurtleUpgrades.diamondSword ); + vanilla.add( ComputerCraft.TurtleUpgrades.diamondShovel ); + vanilla.add( ComputerCraft.TurtleUpgrades.diamondHoe ); + vanilla.add( ComputerCraft.TurtleUpgrades.craftingTable ); + return vanilla; + } + + public static Iterable getUpgrades() + { + return Collections.unmodifiableCollection( upgrades.values() ); + } + + public static boolean suitableForFamily( ComputerFamily family, ITurtleUpgrade upgrade ) + { + return true; + } +} diff --git a/remappedSrc/dan200/computercraft/shared/command/CommandComputerCraft.java b/remappedSrc/dan200/computercraft/shared/command/CommandComputerCraft.java new file mode 100644 index 000000000..ae29a9c12 --- /dev/null +++ b/remappedSrc/dan200/computercraft/shared/command/CommandComputerCraft.java @@ -0,0 +1,368 @@ +/* + * 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.shared.command; + +import com.mojang.brigadier.CommandDispatcher; +import com.mojang.brigadier.arguments.StringArgumentType; +import com.mojang.brigadier.exceptions.CommandSyntaxException; +import dan200.computercraft.ComputerCraft; +import dan200.computercraft.api.peripheral.IPeripheral; +import dan200.computercraft.core.computer.Computer; +import dan200.computercraft.core.computer.ComputerSide; +import dan200.computercraft.core.tracking.ComputerTracker; +import dan200.computercraft.core.tracking.Tracking; +import dan200.computercraft.core.tracking.TrackingContext; +import dan200.computercraft.core.tracking.TrackingField; +import dan200.computercraft.shared.command.text.TableBuilder; +import dan200.computercraft.shared.computer.core.ComputerFamily; +import dan200.computercraft.shared.computer.core.ServerComputer; +import dan200.computercraft.shared.network.Containers; +import net.minecraft.entity.Entity; +import net.minecraft.entity.player.PlayerEntity; +import net.minecraft.text.LiteralText; +import net.minecraft.text.Text; +import net.minecraft.server.command.ServerCommandSource; +import net.minecraft.server.network.ServerPlayerEntity; +import net.minecraft.server.world.ServerWorld; +import net.minecraft.util.math.BlockPos; +import net.minecraft.world.World; + +import javax.annotation.Nonnull; +import java.util.*; + +import static dan200.computercraft.shared.command.CommandUtils.isPlayer; +import static dan200.computercraft.shared.command.Exceptions.*; +import static dan200.computercraft.shared.command.arguments.ComputerArgumentType.getComputerArgument; +import static dan200.computercraft.shared.command.arguments.ComputerArgumentType.oneComputer; +import static dan200.computercraft.shared.command.arguments.ComputersArgumentType.*; +import static dan200.computercraft.shared.command.arguments.TrackingFieldArgumentType.trackingField; +import static dan200.computercraft.shared.command.builder.CommandBuilder.args; +import static dan200.computercraft.shared.command.builder.CommandBuilder.command; +import static dan200.computercraft.shared.command.builder.HelpingArgumentBuilder.choice; +import static dan200.computercraft.shared.command.text.ChatHelpers.*; +import static net.minecraft.server.command.CommandManager.literal; + +public final class CommandComputerCraft +{ + public static final UUID SYSTEM_UUID = new UUID( 0, 0 ); + + private static final int DUMP_LIST_ID = 5373952; + private static final int DUMP_SINGLE_ID = 1844510720; + private static final int TRACK_ID = 373882880; + + private CommandComputerCraft() + { + } + + public static void register( CommandDispatcher dispatcher ) + { + dispatcher.register( choice( "computercraft" ) + .then( literal( "dump" ) + .requires( UserLevel.OWNER_OP ) + .executes( context -> { + TableBuilder table = new TableBuilder( DUMP_LIST_ID, "Computer", "On", "Position" ); + + ServerCommandSource source = context.getSource(); + List computers = new ArrayList<>( ComputerCraft.serverComputerRegistry.getComputers() ); + + // Unless we're on a server, limit the number of rows we can send. + World world = source.getWorld(); + BlockPos pos = new BlockPos( source.getPosition() ); + + computers.sort( ( a, b ) -> { + if( a.getWorld() == b.getWorld() && a.getWorld() == world ) + { + return Double.compare( a.getPosition().getSquaredDistance( pos ), b.getPosition().getSquaredDistance( pos ) ); + } + else if( a.getWorld() == world ) + { + return -1; + } + else if( b.getWorld() == world ) + { + return 1; + } + else + { + return Integer.compare( a.getInstanceID(), b.getInstanceID() ); + } + } ); + + for( ServerComputer computer : computers ) + { + table.row( + linkComputer( source, computer, computer.getID() ), + bool( computer.isOn() ), + linkPosition( source, computer ) + ); + } + + table.display( context.getSource() ); + return computers.size(); + } ) + .then( args() + .arg( "computer", oneComputer() ) + .executes( context -> { + ServerComputer computer = getComputerArgument( context, "computer" ); + + TableBuilder table = new TableBuilder( DUMP_SINGLE_ID ); + table.row( header( "Instance" ), text( Integer.toString( computer.getInstanceID() ) ) ); + table.row( header( "Id" ), text( Integer.toString( computer.getID() ) ) ); + table.row( header( "Label" ), text( computer.getLabel() ) ); + table.row( header( "On" ), bool( computer.isOn() ) ); + table.row( header( "Position" ), linkPosition( context.getSource(), computer ) ); + table.row( header( "Family" ), text( computer.getFamily().toString() ) ); + + for( ComputerSide side : ComputerSide.values() ) + { + IPeripheral peripheral = computer.getPeripheral( side ); + if( peripheral != null ) + { + table.row( header( "Peripheral " + side.getName() ), text( peripheral.getType() ) ); + } + } + + table.display( context.getSource() ); + return 1; + } ) ) ) + + .then( command( "shutdown" ) + .requires( UserLevel.OWNER_OP ) + .argManyValue( "computers", manyComputers(), s -> ComputerCraft.serverComputerRegistry.getComputers() ) + .executes( ( context, computers ) -> { + int shutdown = 0; + for( ServerComputer computer : unwrap( context.getSource(), computers ) ) + { + if( computer.isOn() ) shutdown++; + computer.shutdown(); + } + context.getSource().sendFeedback( translate( "commands.computercraft.shutdown.done", shutdown, computers.size() ), false ); + return shutdown; + } ) ) + + .then( command( "turn-on" ) + .requires( UserLevel.OWNER_OP ) + .argManyValue( "computers", manyComputers(), s -> ComputerCraft.serverComputerRegistry.getComputers() ) + .executes( ( context, computers ) -> { + int on = 0; + for( ServerComputer computer : unwrap( context.getSource(), computers ) ) + { + if( !computer.isOn() ) on++; + computer.turnOn(); + } + context.getSource().sendFeedback( translate( "commands.computercraft.turn_on.done", on, computers.size() ), false ); + return on; + } ) ) + + .then( command( "tp" ) + .requires( UserLevel.OP ) + .arg( "computer", oneComputer() ) + .executes( context -> { + ServerComputer computer = getComputerArgument( context, "computer" ); + World world = computer.getWorld(); + BlockPos pos = computer.getPosition(); + + if( world == null || pos == null ) throw TP_NOT_THERE.create(); + + Entity entity = context.getSource().getEntityOrThrow(); + if( !(entity instanceof ServerPlayerEntity) ) throw TP_NOT_PLAYER.create(); + + ServerPlayerEntity player = (ServerPlayerEntity) entity; + if( player.getEntityWorld() == world ) + { + player.networkHandler.teleportRequest( pos.getX() + 0.5, pos.getY(), pos.getZ() + 0.5, 0, 0, Collections.emptySet() ); + } + else + { + player.teleport( (ServerWorld) world, pos.getX() + 0.5, pos.getY(), pos.getZ() + 0.5, 0, 0 ); + } + + return 1; + } ) ) + + .then( command( "queue" ) + .requires( UserLevel.ANYONE ) + .arg( "computer", manyComputers() ) + .argManyValue( "args", StringArgumentType.string(), Collections.emptyList() ) + .executes( ( ctx, args ) -> { + Collection computers = getComputersArgument( ctx, "computer" ); + Object[] rest = args.toArray(); + + int queued = 0; + for( ServerComputer computer : computers ) + { + if( computer.getFamily() == ComputerFamily.Command && computer.isOn() ) + { + computer.queueEvent( "computer_command", rest ); + queued++; + } + } + + return queued; + } ) ) + + .then( command( "view" ) + .requires( UserLevel.OP ) + .arg( "computer", oneComputer() ) + .executes( context -> { + ServerPlayerEntity player = context.getSource().getPlayer(); + ServerComputer computer = getComputerArgument( context, "computer" ); + Containers.openComputerGUI( player, computer ); + return 1; + } ) ) + + .then( choice( "track" ) + .then( command( "start" ) + .requires( UserLevel.OWNER_OP ) + .executes( context -> { + getTimingContext( context.getSource() ).start(); + + String stopCommand = "/computercraft track stop"; + context.getSource().sendFeedback( translate( "commands.computercraft.track.start.stop", + link( text( stopCommand ), stopCommand, translate( "commands.computercraft.track.stop.action" ) ) ), false ); + return 1; + } ) ) + + .then( command( "stop" ) + .requires( UserLevel.OWNER_OP ) + .executes( context -> { + TrackingContext timings = getTimingContext( context.getSource() ); + if( !timings.stop() ) throw NOT_TRACKING_EXCEPTION.create(); + displayTimings( context.getSource(), timings.getImmutableTimings(), TrackingField.AVERAGE_TIME, DEFAULT_FIELDS ); + return 1; + } ) ) + + .then( command( "dump" ) + .requires( UserLevel.OWNER_OP ) + .argManyValue( "fields", trackingField(), DEFAULT_FIELDS ) + .executes( ( context, fields ) -> { + TrackingField sort; + if( fields.size() == 1 && DEFAULT_FIELDS.contains( fields.get( 0 ) ) ) + { + sort = fields.get( 0 ); + fields = DEFAULT_FIELDS; + } + else + { + sort = fields.get( 0 ); + } + + return displayTimings( context.getSource(), sort, fields ); + } ) ) ) + ); + } + + private static Text linkComputer( ServerCommandSource source, ServerComputer serverComputer, int computerId ) + { + LiteralText out = new LiteralText( "" ); + + // Append the computer instance + if( serverComputer == null ) + { + out.append( text( "?" ) ); + } + else + { + out.append( link( + text( Integer.toString( serverComputer.getInstanceID() ) ), + "/computercraft dump " + serverComputer.getInstanceID(), + translate( "commands.computercraft.dump.action" ) + ) ); + } + + // And ID + out.append( " (id " + computerId + ")" ); + + // And, if we're a player, some useful links + if( serverComputer != null && UserLevel.OP.test( source ) && isPlayer( source ) ) + { + out + .append( " " ) + .append( link( + text( "\u261b" ), + "/computercraft tp " + serverComputer.getInstanceID(), + translate( "commands.computercraft.tp.action" ) + ) ) + .append( " " ) + .append( link( + text( "\u20e2" ), + "/computercraft view " + serverComputer.getInstanceID(), + translate( "commands.computercraft.view.action" ) + ) ); + } + + return out; + } + + private static Text linkPosition( ServerCommandSource context, ServerComputer computer ) + { + if( UserLevel.OP.test( context ) ) + { + return link( + position( computer.getPosition() ), + "/computercraft tp " + computer.getInstanceID(), + translate( "commands.computercraft.tp.action" ) + ); + } + else + { + return position( computer.getPosition() ); + } + } + + @Nonnull + private static TrackingContext getTimingContext( ServerCommandSource source ) + { + Entity entity = source.getEntity(); + return entity instanceof PlayerEntity ? Tracking.getContext( entity.getUuid() ) : Tracking.getContext( SYSTEM_UUID ); + } + + private static final List DEFAULT_FIELDS = Arrays.asList( TrackingField.TASKS, TrackingField.TOTAL_TIME, TrackingField.AVERAGE_TIME, TrackingField.MAX_TIME ); + + private static int displayTimings( ServerCommandSource source, TrackingField sortField, List fields ) throws CommandSyntaxException + { + return displayTimings( source, getTimingContext( source ).getTimings(), sortField, fields ); + } + + private static int displayTimings( ServerCommandSource source, @Nonnull List timings, @Nonnull TrackingField sortField, @Nonnull List fields ) throws CommandSyntaxException + { + if( timings.isEmpty() ) throw NO_TIMINGS_EXCEPTION.create(); + + Map lookup = new HashMap<>(); + int maxId = 0, maxInstance = 0; + for( ServerComputer server : ComputerCraft.serverComputerRegistry.getComputers() ) + { + lookup.put( server.getComputer(), server ); + + if( server.getInstanceID() > maxInstance ) maxInstance = server.getInstanceID(); + if( server.getID() > maxId ) maxId = server.getID(); + } + + timings.sort( Comparator.comparing( x -> x.get( sortField ) ).reversed() ); + + Text[] headers = new Text[1 + fields.size()]; + headers[0] = translate( "commands.computercraft.track.dump.computer" ); + for( int i = 0; i < fields.size(); i++ ) headers[i + 1] = translate( fields.get( i ).translationKey() ); + TableBuilder table = new TableBuilder( TRACK_ID, headers ); + + for( ComputerTracker entry : timings ) + { + Computer computer = entry.getComputer(); + ServerComputer serverComputer = computer == null ? null : lookup.get( computer ); + + Text computerComponent = linkComputer( source, serverComputer, entry.getComputerId() ); + + Text[] row = new Text[1 + fields.size()]; + row[0] = computerComponent; + for( int i = 0; i < fields.size(); i++ ) row[i + 1] = text( entry.getFormatted( fields.get( i ) ) ); + table.row( row ); + } + + table.display( source ); + return timings.size(); + } +} diff --git a/remappedSrc/dan200/computercraft/shared/command/CommandCopy.java b/remappedSrc/dan200/computercraft/shared/command/CommandCopy.java new file mode 100644 index 000000000..b60dc2689 --- /dev/null +++ b/remappedSrc/dan200/computercraft/shared/command/CommandCopy.java @@ -0,0 +1,61 @@ +/* + * 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.shared.command; + +import com.mojang.brigadier.CommandDispatcher; +import com.mojang.brigadier.arguments.StringArgumentType; +import net.minecraft.client.MinecraftClient; +import net.minecraft.text.ClickEvent; +import net.minecraft.text.HoverEvent; +import net.minecraft.text.LiteralText; +import net.minecraft.text.TranslatableText; +import net.minecraft.server.command.ServerCommandSource; + +import static net.minecraft.server.command.CommandManager.argument; +import static net.minecraft.server.command.CommandManager.literal; + +public final class CommandCopy +{ + private static final String PREFIX = "/computercraft copy "; + + private CommandCopy() + { + } + + public static void register( CommandDispatcher registry ) + { + registry.register( literal( "computercraft" ) + .then( literal( "copy" ) ) + .then( argument( "message", StringArgumentType.greedyString() ) ) + .executes( context -> { + MinecraftClient.getInstance().keyboard.setClipboard( context.getArgument( "message", String.class ) ); + return 1; + } ) + ); + } + + public static boolean onClientSendMessage( String message ) + { + // Emulate the command on the client side + if( message.startsWith( PREFIX ) ) + { + MinecraftClient.getInstance().keyboard.setClipboard( message.substring( PREFIX.length() ) ); + return true; + } + + return false; + } + + public static LiteralText createCopyText( String text ) + { + LiteralText name = new LiteralText( text ); + name.getStyle() + .withClickEvent( new ClickEvent( ClickEvent.Action.RUN_COMMAND, PREFIX + text ) ) + .setHoverEvent( new HoverEvent( HoverEvent.Action.SHOW_TEXT, new TranslatableText( "gui.computercraft.tooltip.copy" ) ) ); + return name; + } +} diff --git a/remappedSrc/dan200/computercraft/shared/command/CommandUtils.java b/remappedSrc/dan200/computercraft/shared/command/CommandUtils.java new file mode 100644 index 000000000..f09bf4198 --- /dev/null +++ b/remappedSrc/dan200/computercraft/shared/command/CommandUtils.java @@ -0,0 +1,70 @@ +/* + * 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.shared.command; + +import com.mojang.brigadier.context.CommandContext; +import com.mojang.brigadier.suggestion.Suggestions; +import com.mojang.brigadier.suggestion.SuggestionsBuilder; +import dan200.computercraft.api.turtle.event.FakePlayer; +import net.minecraft.entity.Entity; +import net.minecraft.server.command.CommandSource; +import net.minecraft.server.command.ServerCommandSource; +import net.minecraft.server.network.ServerPlayerEntity; + +import java.util.Arrays; +import java.util.Locale; +import java.util.concurrent.CompletableFuture; +import java.util.function.Function; + +public final class CommandUtils +{ + private CommandUtils() {} + + public static boolean isPlayer( ServerCommandSource output ) + { + Entity sender = output.getEntity(); + return sender instanceof ServerPlayerEntity + && !(sender instanceof FakePlayer) + && ((ServerPlayerEntity) sender).networkHandler != null; + } + + @SuppressWarnings( "unchecked" ) + public static CompletableFuture suggestOnServer( CommandContext context, SuggestionsBuilder builder, Function, CompletableFuture> supplier ) + { + Object source = context.getSource(); + if( !(source instanceof CommandSource) ) + { + return Suggestions.empty(); + } + else if( source instanceof ServerCommandSource ) + { + return supplier.apply( (CommandContext) context ); + } + else + { + return ((CommandSource) source).getCompletions( (CommandContext) context, builder ); + } + } + + public static CompletableFuture suggest( SuggestionsBuilder builder, Iterable candidates, Function toString ) + { + String remaining = builder.getRemaining().toLowerCase( Locale.ROOT ); + for( T choice : candidates ) + { + String name = toString.apply( choice ); + if( !name.toLowerCase( Locale.ROOT ).startsWith( remaining ) ) continue; + builder.suggest( name ); + } + + return builder.buildFuture(); + } + + public static CompletableFuture suggest( SuggestionsBuilder builder, T[] candidates, Function toString ) + { + return suggest( builder, Arrays.asList( candidates ), toString ); + } +} diff --git a/remappedSrc/dan200/computercraft/shared/command/Exceptions.java b/remappedSrc/dan200/computercraft/shared/command/Exceptions.java new file mode 100644 index 000000000..0368ff932 --- /dev/null +++ b/remappedSrc/dan200/computercraft/shared/command/Exceptions.java @@ -0,0 +1,43 @@ +/* + * 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.shared.command; + +import com.mojang.brigadier.exceptions.Dynamic2CommandExceptionType; +import com.mojang.brigadier.exceptions.DynamicCommandExceptionType; +import com.mojang.brigadier.exceptions.SimpleCommandExceptionType; +import net.minecraft.text.TranslatableText; + +public final class Exceptions +{ + public static final DynamicCommandExceptionType COMPUTER_ARG_NONE = translated1( "argument.computercraft.computer.no_matching" ); + public static final Dynamic2CommandExceptionType COMPUTER_ARG_MANY = translated2( "argument.computercraft.computer.many_matching" ); + + public static final DynamicCommandExceptionType TRACKING_FIELD_ARG_NONE = translated1( "argument.computercraft.tracking_field.no_field" ); + + static final SimpleCommandExceptionType NOT_TRACKING_EXCEPTION = translated( "commands.computercraft.track.stop.not_enabled" ); + static final SimpleCommandExceptionType NO_TIMINGS_EXCEPTION = translated( "commands.computercraft.track.dump.no_timings" ); + + static final SimpleCommandExceptionType TP_NOT_THERE = translated( "commands.computercraft.tp.not_there" ); + static final SimpleCommandExceptionType TP_NOT_PLAYER = translated( "commands.computercraft.tp.not_player" ); + + public static final SimpleCommandExceptionType ARGUMENT_EXPECTED = translated( "argument.computercraft.argument_expected" ); + + private static SimpleCommandExceptionType translated( String key ) + { + return new SimpleCommandExceptionType( new TranslatableText( key ) ); + } + + private static DynamicCommandExceptionType translated1( String key ) + { + return new DynamicCommandExceptionType( x -> new TranslatableText( key, x ) ); + } + + private static Dynamic2CommandExceptionType translated2( String key ) + { + return new Dynamic2CommandExceptionType( ( x, y ) -> new TranslatableText( key, x, y ) ); + } +} diff --git a/remappedSrc/dan200/computercraft/shared/command/UserLevel.java b/remappedSrc/dan200/computercraft/shared/command/UserLevel.java new file mode 100644 index 000000000..55ff7f118 --- /dev/null +++ b/remappedSrc/dan200/computercraft/shared/command/UserLevel.java @@ -0,0 +1,73 @@ +/* + * 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.shared.command; + +import net.minecraft.entity.Entity; +import net.minecraft.entity.player.PlayerEntity; +import net.minecraft.server.MinecraftServer; +import net.minecraft.server.command.ServerCommandSource; + +import java.util.function.Predicate; + +/** + * The level a user must be at in order to execute a command. + */ +public enum UserLevel implements Predicate +{ + /** + * Only can be used by the owner of the server: namely the server console or the player in SSP. + */ + OWNER, + + /** + * Can only be used by ops. + */ + OP, + + /** + * Can be used by any op, or the player in SSP. + */ + OWNER_OP, + + /** + * Can be used by anyone. + */ + ANYONE; + + public int toLevel() + { + switch( this ) + { + case OWNER: + return 4; + case OP: + case OWNER_OP: + return 2; + case ANYONE: + default: + return 0; + } + } + + @Override + public boolean test( ServerCommandSource source ) + { + if( this == ANYONE ) return true; + + // We *always* allow level 0 stuff, even if the + MinecraftServer server = source.getMinecraftServer(); + Entity sender = source.getEntity(); + + if( server.isSinglePlayer() && sender instanceof PlayerEntity && + ((PlayerEntity) sender).getGameProfile().getName().equalsIgnoreCase( server.getUserName() ) ) + { + if( this == OWNER || this == OWNER_OP ) return true; + } + + return source.hasPermissionLevel( toLevel() ); + } +} diff --git a/remappedSrc/dan200/computercraft/shared/command/arguments/ArgumentSerializers.java b/remappedSrc/dan200/computercraft/shared/command/arguments/ArgumentSerializers.java new file mode 100644 index 000000000..57505ca0f --- /dev/null +++ b/remappedSrc/dan200/computercraft/shared/command/arguments/ArgumentSerializers.java @@ -0,0 +1,41 @@ +/* + * 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.shared.command.arguments; + +import com.mojang.brigadier.arguments.ArgumentType; +import dan200.computercraft.ComputerCraft; +import net.minecraft.command.arguments.ArgumentTypes; +import net.minecraft.command.arguments.serialize.ArgumentSerializer; +import net.minecraft.command.arguments.serialize.ConstantArgumentSerializer; +import net.minecraft.util.Identifier; + +public final class ArgumentSerializers +{ + @SuppressWarnings( "unchecked" ) + private static > void registerUnsafe( Identifier id, Class type, ArgumentSerializer serializer ) + { + ArgumentTypes.register( id.toString(), type, (ArgumentSerializer) serializer ); + } + + private static > void register( Identifier id, Class type, ArgumentSerializer serializer ) + { + ArgumentTypes.register( id.toString(), type, serializer ); + } + + private static > void register( Identifier id, T instance ) + { + registerUnsafe( id, instance.getClass(), new ConstantArgumentSerializer<>( () -> instance ) ); + } + + public static void register() + { + register( new Identifier( ComputerCraft.MOD_ID, "tracking_field" ), TrackingFieldArgumentType.trackingField() ); + register( new Identifier( ComputerCraft.MOD_ID, "computer" ), ComputerArgumentType.oneComputer() ); + register( new Identifier( ComputerCraft.MOD_ID, "computers" ), ComputersArgumentType.class, new ComputersArgumentType.Serializer() ); + registerUnsafe( new Identifier( ComputerCraft.MOD_ID, "repeat" ), RepeatArgumentType.class, new RepeatArgumentType.Serializer() ); + } +} diff --git a/remappedSrc/dan200/computercraft/shared/command/arguments/ChoiceArgumentType.java b/remappedSrc/dan200/computercraft/shared/command/arguments/ChoiceArgumentType.java new file mode 100644 index 000000000..16d8f8f26 --- /dev/null +++ b/remappedSrc/dan200/computercraft/shared/command/arguments/ChoiceArgumentType.java @@ -0,0 +1,75 @@ +/* + * 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.shared.command.arguments; + +import com.mojang.brigadier.Message; +import com.mojang.brigadier.StringReader; +import com.mojang.brigadier.arguments.ArgumentType; +import com.mojang.brigadier.context.CommandContext; +import com.mojang.brigadier.exceptions.CommandSyntaxException; +import com.mojang.brigadier.exceptions.DynamicCommandExceptionType; +import com.mojang.brigadier.suggestion.Suggestions; +import com.mojang.brigadier.suggestion.SuggestionsBuilder; + +import java.util.*; +import java.util.concurrent.CompletableFuture; +import java.util.function.Function; + +public abstract class ChoiceArgumentType implements ArgumentType +{ + private final Iterable choices; + private final Function name; + private final Function tooltip; + private final DynamicCommandExceptionType exception; + + protected ChoiceArgumentType( Iterable choices, Function name, Function tooltip, DynamicCommandExceptionType exception ) + { + this.choices = choices; + this.name = name; + this.tooltip = tooltip; + this.exception = exception; + } + + @Override + public T parse( StringReader reader ) throws CommandSyntaxException + { + int start = reader.getCursor(); + String name = reader.readUnquotedString(); + + for( T choice : choices ) + { + String choiceName = this.name.apply( choice ); + if( name.equals( choiceName ) ) return choice; + } + + reader.setCursor( start ); + throw exception.createWithContext( reader, name ); + } + + @Override + public CompletableFuture listSuggestions( CommandContext context, SuggestionsBuilder builder ) + { + String remaining = builder.getRemaining().toLowerCase( Locale.ROOT ); + for( T choice : choices ) + { + String name = this.name.apply( choice ); + if( !name.toLowerCase( Locale.ROOT ).startsWith( remaining ) ) continue; + builder.suggest( name, tooltip.apply( choice ) ); + } + + return builder.buildFuture(); + } + + @Override + public Collection getExamples() + { + List items = choices instanceof Collection ? new ArrayList<>( ((Collection) choices).size() ) : new ArrayList<>(); + for( T choice : choices ) items.add( name.apply( choice ) ); + items.sort( Comparator.naturalOrder() ); + return items; + } +} diff --git a/remappedSrc/dan200/computercraft/shared/command/arguments/ComputerArgumentType.java b/remappedSrc/dan200/computercraft/shared/command/arguments/ComputerArgumentType.java new file mode 100644 index 000000000..24d3eb1e5 --- /dev/null +++ b/remappedSrc/dan200/computercraft/shared/command/arguments/ComputerArgumentType.java @@ -0,0 +1,94 @@ +/* + * 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.shared.command.arguments; + +import com.mojang.brigadier.StringReader; +import com.mojang.brigadier.arguments.ArgumentType; +import com.mojang.brigadier.context.CommandContext; +import com.mojang.brigadier.exceptions.CommandSyntaxException; +import com.mojang.brigadier.suggestion.Suggestions; +import com.mojang.brigadier.suggestion.SuggestionsBuilder; +import dan200.computercraft.shared.command.arguments.ComputersArgumentType.ComputersSupplier; +import dan200.computercraft.shared.computer.core.ServerComputer; +import net.minecraft.server.command.ServerCommandSource; + +import java.util.Collection; +import java.util.concurrent.CompletableFuture; + +import static dan200.computercraft.shared.command.Exceptions.COMPUTER_ARG_MANY; + +public final class ComputerArgumentType implements ArgumentType +{ + private static final ComputerArgumentType INSTANCE = new ComputerArgumentType(); + + public static ComputerArgumentType oneComputer() + { + return INSTANCE; + } + + public static ServerComputer getComputerArgument( CommandContext context, String name ) throws CommandSyntaxException + { + return context.getArgument( name, ComputerSupplier.class ).unwrap( context.getSource() ); + } + + private ComputerArgumentType() + { + } + + @Override + public ComputerSupplier parse( StringReader reader ) throws CommandSyntaxException + { + int start = reader.getCursor(); + ComputersSupplier supplier = ComputersArgumentType.someComputers().parse( reader ); + String selector = reader.getString().substring( start, reader.getCursor() ); + + return s -> { + Collection computers = supplier.unwrap( s ); + + if( computers.size() == 1 ) return computers.iterator().next(); + + StringBuilder builder = new StringBuilder(); + boolean first = true; + for( ServerComputer computer : computers ) + { + if( first ) + { + first = false; + } + else + { + builder.append( ", " ); + } + + builder.append( computer.getInstanceID() ); + } + + + // We have an incorrect number of computers: reset and throw an error + reader.setCursor( start ); + throw COMPUTER_ARG_MANY.createWithContext( reader, selector, builder.toString() ); + }; + } + + @Override + public CompletableFuture listSuggestions( CommandContext context, SuggestionsBuilder builder ) + { + return ComputersArgumentType.someComputers().listSuggestions( context, builder ); + } + + @Override + public Collection getExamples() + { + return ComputersArgumentType.someComputers().getExamples(); + } + + @FunctionalInterface + public interface ComputerSupplier + { + ServerComputer unwrap( ServerCommandSource source ) throws CommandSyntaxException; + } +} diff --git a/remappedSrc/dan200/computercraft/shared/command/arguments/ComputersArgumentType.java b/remappedSrc/dan200/computercraft/shared/command/arguments/ComputersArgumentType.java new file mode 100644 index 000000000..5023b6771 --- /dev/null +++ b/remappedSrc/dan200/computercraft/shared/command/arguments/ComputersArgumentType.java @@ -0,0 +1,209 @@ +/* + * 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.shared.command.arguments; + +import com.google.gson.JsonObject; +import com.mojang.brigadier.StringReader; +import com.mojang.brigadier.arguments.ArgumentType; +import com.mojang.brigadier.context.CommandContext; +import com.mojang.brigadier.exceptions.CommandSyntaxException; +import com.mojang.brigadier.suggestion.Suggestions; +import com.mojang.brigadier.suggestion.SuggestionsBuilder; +import dan200.computercraft.ComputerCraft; +import dan200.computercraft.shared.computer.core.ComputerFamily; +import dan200.computercraft.shared.computer.core.ServerComputer; +import net.minecraft.command.arguments.serialize.ArgumentSerializer; +import net.minecraft.network.PacketByteBuf; +import net.minecraft.server.command.ServerCommandSource; +import javax.annotation.Nonnull; +import java.util.*; +import java.util.concurrent.CompletableFuture; +import java.util.function.Function; +import java.util.function.Predicate; +import java.util.stream.Collectors; + +import static dan200.computercraft.shared.command.CommandUtils.suggest; +import static dan200.computercraft.shared.command.CommandUtils.suggestOnServer; +import static dan200.computercraft.shared.command.Exceptions.COMPUTER_ARG_NONE; + +public final class ComputersArgumentType implements ArgumentType +{ + private static final ComputersArgumentType MANY = new ComputersArgumentType( false ); + private static final ComputersArgumentType SOME = new ComputersArgumentType( true ); + + private static final List EXAMPLES = Arrays.asList( + "0", "#0", "@Label", "~Advanced" + ); + + public static ComputersArgumentType manyComputers() + { + return MANY; + } + + public static ComputersArgumentType someComputers() + { + return SOME; + } + + public static Collection getComputersArgument( CommandContext context, String name ) throws CommandSyntaxException + { + return context.getArgument( name, ComputersSupplier.class ).unwrap( context.getSource() ); + } + + private final boolean requireSome; + + private ComputersArgumentType( boolean requireSome ) + { + this.requireSome = requireSome; + } + + @Override + public ComputersSupplier parse( StringReader reader ) throws CommandSyntaxException + { + int start = reader.getCursor(); + char kind = reader.peek(); + ComputersSupplier computers; + if( kind == '@' ) + { + reader.skip(); + String label = reader.readUnquotedString(); + computers = getComputers( x -> Objects.equals( label, x.getLabel() ) ); + } + else if( kind == '~' ) + { + reader.skip(); + String family = reader.readUnquotedString(); + computers = getComputers( x -> x.getFamily().name().equalsIgnoreCase( family ) ); + } + else if( kind == '#' ) + { + reader.skip(); + int id = reader.readInt(); + computers = getComputers( x -> x.getID() == id ); + } + else + { + int instance = reader.readInt(); + computers = s -> { + ServerComputer computer = ComputerCraft.serverComputerRegistry.get( instance ); + return computer == null ? Collections.emptyList() : Collections.singletonList( computer ); + }; + } + + if( requireSome ) + { + String selector = reader.getString().substring( start, reader.getCursor() ); + return source -> { + Collection matched = computers.unwrap( source ); + if( matched.isEmpty() ) throw COMPUTER_ARG_NONE.create( selector ); + return matched; + }; + } + else + { + return computers; + } + } + + @Override + public CompletableFuture listSuggestions( CommandContext context, SuggestionsBuilder builder ) + { + String remaining = builder.getRemaining(); + + // We can run this one on the client, for obvious reasons. + if( remaining.startsWith( "~" ) ) + { + return suggest( builder, ComputerFamily.values(), x -> "~" + x.name() ); + } + + // Verify we've a command source and we're running on the server + return suggestOnServer( context, builder, s -> { + if( remaining.startsWith( "@" ) ) + { + suggestComputers( builder, remaining, x -> { + String label = x.getLabel(); + return label == null ? null : "@" + label; + } ); + } + else if( remaining.startsWith( "#" ) ) + { + suggestComputers( builder, remaining, c -> "#" + c.getID() ); + } + else + { + suggestComputers( builder, remaining, c -> Integer.toString( c.getInstanceID() ) ); + } + + return builder.buildFuture(); + } ); + } + + @Override + public Collection getExamples() + { + return EXAMPLES; + } + + private static void suggestComputers( SuggestionsBuilder builder, String remaining, Function renderer ) + { + remaining = remaining.toLowerCase( Locale.ROOT ); + for( ServerComputer computer : ComputerCraft.serverComputerRegistry.getComputers() ) + { + String converted = renderer.apply( computer ); + if( converted != null && converted.toLowerCase( Locale.ROOT ).startsWith( remaining ) ) + { + builder.suggest( converted ); + } + } + } + + private static ComputersSupplier getComputers( Predicate predicate ) + { + return s -> Collections.unmodifiableList( ComputerCraft.serverComputerRegistry + .getComputers() + .stream() + .filter( predicate ) + .collect( Collectors.toList() ) + ); + } + + public static class Serializer implements ArgumentSerializer + { + + @Override + public void toPacket( @Nonnull ComputersArgumentType arg, @Nonnull PacketByteBuf buf ) + { + buf.writeBoolean( arg.requireSome ); + } + + @Nonnull + @Override + public ComputersArgumentType fromPacket( @Nonnull PacketByteBuf buf ) + { + return buf.readBoolean() ? SOME : MANY; + } + + @Override + public void toJson( @Nonnull ComputersArgumentType arg, @Nonnull JsonObject json ) + { + json.addProperty( "requireSome", arg.requireSome ); + } + } + + @FunctionalInterface + public interface ComputersSupplier + { + Collection unwrap( ServerCommandSource source ) throws CommandSyntaxException; + } + + public static Set unwrap( ServerCommandSource source, Collection suppliers ) throws CommandSyntaxException + { + Set computers = new HashSet<>(); + for( ComputersSupplier supplier : suppliers ) computers.addAll( supplier.unwrap( source ) ); + return computers; + } +} diff --git a/remappedSrc/dan200/computercraft/shared/command/arguments/RepeatArgumentType.java b/remappedSrc/dan200/computercraft/shared/command/arguments/RepeatArgumentType.java new file mode 100644 index 000000000..f64195720 --- /dev/null +++ b/remappedSrc/dan200/computercraft/shared/command/arguments/RepeatArgumentType.java @@ -0,0 +1,165 @@ +/* + * 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.shared.command.arguments; + +import com.google.gson.JsonObject; +import com.mojang.brigadier.Message; +import com.mojang.brigadier.StringReader; +import com.mojang.brigadier.arguments.ArgumentType; +import com.mojang.brigadier.context.CommandContext; +import com.mojang.brigadier.exceptions.CommandSyntaxException; +import com.mojang.brigadier.exceptions.SimpleCommandExceptionType; +import com.mojang.brigadier.suggestion.Suggestions; +import com.mojang.brigadier.suggestion.SuggestionsBuilder; +import net.minecraft.command.arguments.ArgumentTypes; +import net.minecraft.command.arguments.serialize.ArgumentSerializer; +import net.minecraft.network.PacketByteBuf; +import net.minecraft.text.LiteralText; +import net.minecraft.text.Text; +import javax.annotation.Nonnull; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.List; +import java.util.concurrent.CompletableFuture; +import java.util.function.BiConsumer; + +/** + * Reads one argument multiple times. + * + * Note that this must be the last element in an argument chain: in order to improve the quality of error messages, + * we will always try to consume another argument while there is input remaining. + * + * One problem with how parsers function, is that they must consume some input: and thus we + * + * @param The type of each value returned + * @param The type of the inner parser. This will normally be a {@link List} or {@code T}. + */ +public final class RepeatArgumentType implements ArgumentType> +{ + private final ArgumentType child; + private final BiConsumer, U> appender; + private final boolean flatten; + private final SimpleCommandExceptionType some; + + private RepeatArgumentType( ArgumentType child, BiConsumer, U> appender, boolean flatten, SimpleCommandExceptionType some ) + { + this.child = child; + this.appender = appender; + this.flatten = flatten; + this.some = some; + } + + public static RepeatArgumentType some( ArgumentType appender, SimpleCommandExceptionType missing ) + { + return new RepeatArgumentType<>( appender, List::add, true, missing ); + } + + public static RepeatArgumentType> someFlat( ArgumentType> appender, SimpleCommandExceptionType missing ) + { + return new RepeatArgumentType<>( appender, List::addAll, true, missing ); + } + + @Override + public List parse( StringReader reader ) throws CommandSyntaxException + { + boolean hadSome = false; + List out = new ArrayList<>(); + while( true ) + { + reader.skipWhitespace(); + if( !reader.canRead() ) break; + + int startParse = reader.getCursor(); + appender.accept( out, child.parse( reader ) ); + hadSome = true; + + if( reader.getCursor() == startParse ) + { + throw new IllegalStateException( child + " did not consume any input on " + reader.getRemaining() ); + } + } + + // Note that each child may return an empty list, we just require that some actual input + // was consumed. + // We should probably review that this is sensible in the future. + if( !hadSome ) throw some.createWithContext( reader ); + + return Collections.unmodifiableList( out ); + } + + @Override + public CompletableFuture listSuggestions( CommandContext context, SuggestionsBuilder builder ) + { + StringReader reader = new StringReader( builder.getInput() ); + reader.setCursor( builder.getStart() ); + int previous = reader.getCursor(); + while( reader.canRead() ) + { + try + { + child.parse( reader ); + } + catch( CommandSyntaxException e ) + { + break; + } + + int cursor = reader.getCursor(); + reader.skipWhitespace(); + if( cursor == reader.getCursor() ) break; + previous = reader.getCursor(); + } + + reader.setCursor( previous ); + return child.listSuggestions( context, builder.createOffset( previous ) ); + } + + @Override + public Collection getExamples() + { + return child.getExamples(); + } + + public static class Serializer implements ArgumentSerializer> + { + @Override + public void toPacket( RepeatArgumentType arg, PacketByteBuf buf ) + { + buf.writeBoolean( arg.flatten ); + ArgumentTypes.toPacket( buf, arg.child ); + buf.writeText( getMessage( arg ) ); + } + + @Nonnull + @Override + @SuppressWarnings( { "unchecked", "rawtypes" } ) + public RepeatArgumentType fromPacket( @Nonnull PacketByteBuf buf ) + { + boolean isList = buf.readBoolean(); + ArgumentType child = ArgumentTypes.fromPacket( buf ); + Text message = buf.readText(); + BiConsumer, ?> appender = isList ? ( list, x ) -> list.addAll( (Collection) x ) : List::add; + return new RepeatArgumentType( child, appender, isList, new SimpleCommandExceptionType( message ) ); + } + + @Override + public void toJson( @Nonnull RepeatArgumentType arg, @Nonnull JsonObject json ) + { + json.addProperty( "flatten", arg.flatten ); + json.addProperty( "child", "<>" ); // TODO: Potentially serialize this using reflection. + json.addProperty( "error", LiteralText.Serializer.toJson( getMessage( arg ) ) ); + } + + private static LiteralText getMessage( RepeatArgumentType arg ) + { + Message message = arg.some.create().getRawMessage(); + if( message instanceof LiteralText ) return (LiteralText) message; + return new LiteralText( message.getString() ); + } + } +} diff --git a/remappedSrc/dan200/computercraft/shared/command/arguments/TrackingFieldArgumentType.java b/remappedSrc/dan200/computercraft/shared/command/arguments/TrackingFieldArgumentType.java new file mode 100644 index 000000000..209a8d343 --- /dev/null +++ b/remappedSrc/dan200/computercraft/shared/command/arguments/TrackingFieldArgumentType.java @@ -0,0 +1,27 @@ +/* + * 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.shared.command.arguments; + +import dan200.computercraft.core.tracking.TrackingField; +import dan200.computercraft.shared.command.Exceptions; + +import static dan200.computercraft.shared.command.text.ChatHelpers.translate; + +public final class TrackingFieldArgumentType extends ChoiceArgumentType +{ + private static final TrackingFieldArgumentType INSTANCE = new TrackingFieldArgumentType(); + + private TrackingFieldArgumentType() + { + super( TrackingField.fields().values(), TrackingField::id, x -> translate( x.translationKey() ), Exceptions.TRACKING_FIELD_ARG_NONE ); + } + + public static TrackingFieldArgumentType trackingField() + { + return INSTANCE; + } +} diff --git a/remappedSrc/dan200/computercraft/shared/command/builder/ArgCommand.java b/remappedSrc/dan200/computercraft/shared/command/builder/ArgCommand.java new file mode 100644 index 000000000..97d08d27c --- /dev/null +++ b/remappedSrc/dan200/computercraft/shared/command/builder/ArgCommand.java @@ -0,0 +1,20 @@ +/* + * 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.shared.command.builder; + +import com.mojang.brigadier.Command; +import com.mojang.brigadier.context.CommandContext; +import com.mojang.brigadier.exceptions.CommandSyntaxException; + +/** + * A {@link Command} which accepts an argument + */ +@FunctionalInterface +public interface ArgCommand +{ + int run( CommandContext ctx, T arg ) throws CommandSyntaxException; +} diff --git a/remappedSrc/dan200/computercraft/shared/command/builder/CommandBuilder.java b/remappedSrc/dan200/computercraft/shared/command/builder/CommandBuilder.java new file mode 100644 index 000000000..9cdcfc4bb --- /dev/null +++ b/remappedSrc/dan200/computercraft/shared/command/builder/CommandBuilder.java @@ -0,0 +1,126 @@ +/* + * 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.shared.command.builder; + +import com.mojang.brigadier.Command; +import com.mojang.brigadier.arguments.ArgumentType; +import com.mojang.brigadier.builder.ArgumentBuilder; +import com.mojang.brigadier.builder.RequiredArgumentBuilder; +import com.mojang.brigadier.context.CommandContext; +import com.mojang.brigadier.tree.CommandNode; +import dan200.computercraft.shared.command.arguments.RepeatArgumentType; +import net.minecraft.server.command.ServerCommandSource; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.function.Predicate; +import java.util.function.Supplier; + +import static dan200.computercraft.shared.command.Exceptions.ARGUMENT_EXPECTED; +import static dan200.computercraft.shared.command.builder.HelpingArgumentBuilder.literal; + +/** + * An alternative way of building command nodes, so one does not have to nest + * {@link ArgumentBuilder#then(CommandNode)}s. + */ +public class CommandBuilder implements CommandNodeBuilder> +{ + private List> args = new ArrayList<>(); + private Predicate requires; + + public static CommandBuilder args() + { + return new CommandBuilder<>(); + } + + public static CommandBuilder command( String literal ) + { + CommandBuilder builder = new CommandBuilder<>(); + builder.args.add( literal( literal ) ); + return builder; + } + + public CommandBuilder requires( Predicate predicate ) + { + requires = requires == null ? predicate : requires.and( predicate ); + return this; + } + + public CommandBuilder arg( String name, ArgumentType type ) + { + args.add( RequiredArgumentBuilder.argument( name, type ) ); + return this; + } + + public CommandNodeBuilder>> argManyValue( String name, ArgumentType type, List empty ) + { + return argMany( name, type, () -> empty ); + } + + public CommandNodeBuilder>> argManyValue( String name, ArgumentType type, T defaultValue ) + { + return argManyValue( name, type, Collections.singletonList( defaultValue ) ); + } + + public CommandNodeBuilder>> argMany( String name, ArgumentType type, Supplier> empty ) + { + return argMany( name, RepeatArgumentType.some( type, ARGUMENT_EXPECTED ), empty ); + } + + public CommandNodeBuilder>> argManyFlatten( String name, ArgumentType> type, Supplier> empty ) + { + return argMany( name, RepeatArgumentType.someFlat( type, ARGUMENT_EXPECTED ), empty ); + } + + private CommandNodeBuilder>> argMany( String name, RepeatArgumentType type, Supplier> empty ) + { + if( args.isEmpty() ) throw new IllegalStateException( "Cannot have empty arg chain builder" ); + + return command -> { + // The node for no arguments + ArgumentBuilder tail = tail( ctx -> command.run( ctx, empty.get() ) ); + + // The node for one or more arguments + ArgumentBuilder moreArg = RequiredArgumentBuilder + .>argument( name, type ) + .executes( ctx -> command.run( ctx, getList( ctx, name ) ) ); + + // Chain all of them together! + tail.then( moreArg ); + return link( tail ); + }; + } + + @SuppressWarnings( "unchecked" ) + private static List getList( CommandContext context, String name ) + { + return (List) context.getArgument( name, List.class ); + } + + @Override + public CommandNode executes( Command command ) + { + if( args.isEmpty() ) throw new IllegalStateException( "Cannot have empty arg chain builder" ); + + return link( tail( command ) ); + } + + private ArgumentBuilder tail( Command command ) + { + ArgumentBuilder defaultTail = args.get( args.size() - 1 ); + defaultTail.executes( command ); + if( requires != null ) defaultTail.requires( requires ); + return defaultTail; + } + + private CommandNode link( ArgumentBuilder tail ) + { + for( int i = args.size() - 2; i >= 0; i-- ) tail = args.get( i ).then( tail ); + return tail.build(); + } +} diff --git a/remappedSrc/dan200/computercraft/shared/command/builder/CommandNodeBuilder.java b/remappedSrc/dan200/computercraft/shared/command/builder/CommandNodeBuilder.java new file mode 100644 index 000000000..4b34ca483 --- /dev/null +++ b/remappedSrc/dan200/computercraft/shared/command/builder/CommandNodeBuilder.java @@ -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.shared.command.builder; + +import com.mojang.brigadier.tree.CommandNode; + +/** + * A builder which generates a {@link CommandNode} from the provided action. + */ +@FunctionalInterface +public interface CommandNodeBuilder +{ + /** + * Generate a command node which executes this command. + * + * @param command The command to run + * @return The constructed node. + */ + CommandNode executes( T command ); +} diff --git a/remappedSrc/dan200/computercraft/shared/command/builder/HelpingArgumentBuilder.java b/remappedSrc/dan200/computercraft/shared/command/builder/HelpingArgumentBuilder.java new file mode 100644 index 000000000..d473e178a --- /dev/null +++ b/remappedSrc/dan200/computercraft/shared/command/builder/HelpingArgumentBuilder.java @@ -0,0 +1,205 @@ +/* + * 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.shared.command.builder; + +import com.mojang.brigadier.Command; +import com.mojang.brigadier.CommandDispatcher; +import com.mojang.brigadier.builder.ArgumentBuilder; +import com.mojang.brigadier.builder.LiteralArgumentBuilder; +import com.mojang.brigadier.context.CommandContext; +import com.mojang.brigadier.tree.CommandNode; +import com.mojang.brigadier.tree.LiteralCommandNode; +import net.minecraft.text.ClickEvent; +import net.minecraft.text.LiteralText; +import net.minecraft.text.Text; +import net.minecraft.util.Formatting; +import net.minecraft.server.command.ServerCommandSource; + +import javax.annotation.Nonnull; +import java.util.ArrayList; +import java.util.Collection; + +import static dan200.computercraft.shared.command.text.ChatHelpers.coloured; +import static dan200.computercraft.shared.command.text.ChatHelpers.translate; + +/** + * An alternative to {@link LiteralArgumentBuilder} which also provides a {@code /... help} command, and defaults + * to that command when no arguments are given. + */ +public final class HelpingArgumentBuilder extends LiteralArgumentBuilder +{ + private final Collection children = new ArrayList<>(); + + private HelpingArgumentBuilder( String literal ) + { + super( literal ); + } + + public static HelpingArgumentBuilder choice( String literal ) + { + return new HelpingArgumentBuilder( literal ); + } + + @Override + public LiteralArgumentBuilder executes( final Command command ) + { + throw new IllegalStateException( "Cannot use executes on a HelpingArgumentBuilder" ); + } + + @Override + public LiteralArgumentBuilder then( final ArgumentBuilder argument ) + { + if( getRedirect() != null ) throw new IllegalStateException( "Cannot add children to a redirected node" ); + + if( argument instanceof HelpingArgumentBuilder ) + { + children.add( (HelpingArgumentBuilder) argument ); + } + else if( argument instanceof LiteralArgumentBuilder ) + { + super.then( argument ); + } + else + { + throw new IllegalStateException( "HelpingArgumentBuilder can only accept literal children" ); + } + + return this; + } + + @Override + public LiteralArgumentBuilder then( CommandNode argument ) + { + if( !(argument instanceof LiteralCommandNode) ) + { + throw new IllegalStateException( "HelpingArgumentBuilder can only accept literal children" ); + } + return super.then( argument ); + } + + @Override + public LiteralCommandNode build() + { + return buildImpl( getLiteral().replace( '-', '_' ), getLiteral() ); + } + + private LiteralCommandNode build( @Nonnull String id, @Nonnull String command ) + { + return buildImpl( id + "." + getLiteral().replace( '-', '_' ), command + " " + getLiteral() ); + } + + private LiteralCommandNode buildImpl( String id, String command ) + { + HelpCommand helpCommand = new HelpCommand( id, command ); + LiteralCommandNode node = new LiteralCommandNode<>( getLiteral(), helpCommand, getRequirement(), getRedirect(), getRedirectModifier(), isFork() ); + helpCommand.node = node; + + // Set up a /... help command + LiteralArgumentBuilder helpNode = LiteralArgumentBuilder.literal( "help" ) + .requires( x -> getArguments().stream().anyMatch( y -> y.getRequirement().test( x ) ) ) + .executes( helpCommand ); + + // Add all normal command children to this and the help node + for( CommandNode child : getArguments() ) + { + node.addChild( child ); + + helpNode.then( LiteralArgumentBuilder.literal( child.getName() ) + .requires( child.getRequirement() ) + .executes( helpForChild( child, id, command ) ) + .build() + ); + } + + // And add alternative versions of which forward instead + for( HelpingArgumentBuilder childBuilder : children ) + { + LiteralCommandNode child = childBuilder.build( id, command ); + node.addChild( child ); + helpNode.then( LiteralArgumentBuilder.literal( child.getName() ) + .requires( child.getRequirement() ) + .executes( helpForChild( child, id, command ) ) + .redirect( child.getChild( "help" ) ) + .build() + ); + } + + node.addChild( helpNode.build() ); + + return node; + } + + private static final Formatting HEADER = Formatting.LIGHT_PURPLE; + private static final Formatting SYNOPSIS = Formatting.AQUA; + private static final Formatting NAME = Formatting.GREEN; + + private static final class HelpCommand implements Command + { + private final String id; + private final String command; + LiteralCommandNode node; + + private HelpCommand( String id, String command ) + { + this.id = id; + this.command = command; + } + + @Override + public int run( CommandContext context ) + { + context.getSource().sendFeedback( getHelp( context, node, id, command ), false ); + return 0; + } + } + + private static Command helpForChild( CommandNode node, String id, String command ) + { + return context -> { + context.getSource().sendFeedback( getHelp( context, node, id + "." + node.getName().replace( '-', '_' ), command + " " + node.getName() ), false ); + return 0; + }; + } + + private static Text getHelp( CommandContext context, CommandNode node, String id, String command ) + { + // An ugly hack to extract usage information from the dispatcher. We generate a temporary node, generate + // the shorthand usage, and emit that. + CommandDispatcher dispatcher = context.getSource().getMinecraftServer().getCommandManager().getDispatcher(); + CommandNode temp = new LiteralCommandNode<>( "_", null, x -> true, null, null, false ); + temp.addChild( node ); + String usage = dispatcher.getSmartUsage( temp, context.getSource() ).get( node ).substring( node.getName().length() ); + + Text output = new LiteralText( "" ) + .append( coloured( "/" + command + usage, HEADER ) ) + .append( " " ) + .append( coloured( translate( "commands." + id + ".synopsis" ), SYNOPSIS ) ) + .append( "\n" ) + .append( translate( "commands." + id + ".desc" ) ); + + for( CommandNode child : node.getChildren() ) + { + if( !child.getRequirement().test( context.getSource() ) || !(child instanceof LiteralCommandNode) ) + { + continue; + } + + output.append( "\n" ); + + Text component = coloured( child.getName(), NAME ); + component.getStyle().withClickEvent( new ClickEvent( + ClickEvent.Action.SUGGEST_COMMAND, + "/" + command + " " + child.getName() + ) ); + output.append( component ); + + output.append( " - " ).append( translate( "commands." + id + "." + child.getName() + ".synopsis" ) ); + } + + return output; + } +} diff --git a/remappedSrc/dan200/computercraft/shared/command/text/ChatHelpers.java b/remappedSrc/dan200/computercraft/shared/command/text/ChatHelpers.java new file mode 100644 index 000000000..df301613b --- /dev/null +++ b/remappedSrc/dan200/computercraft/shared/command/text/ChatHelpers.java @@ -0,0 +1,88 @@ +/* + * 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.shared.command.text; + +import net.minecraft.text.*; +import net.minecraft.util.Formatting; +import net.minecraft.util.math.BlockPos; + +/** + * Various helpers for building chat messages + */ +public final class ChatHelpers +{ + private static final Formatting HEADER = Formatting.LIGHT_PURPLE; + + private ChatHelpers() {} + + public static Text coloured( String text, Formatting colour ) + { + LiteralText component = new LiteralText( text == null ? "" : text ); + component.getStyle().withColor( colour ); + return component; + } + + public static T coloured( T component, Formatting colour ) + { + component.getStyle().withColor( colour ); + return component; + } + + public static Text text( String text ) + { + return new LiteralText( text == null ? "" : text ); + } + + public static Text translate( String text ) + { + return new TranslatableText( text == null ? "" : text ); + } + + public static Text translate( String text, Object... args ) + { + return new TranslatableText( text == null ? "" : text, args ); + } + + public static Text list( Text... children ) + { + Text component = new LiteralText( "" ); + for( Text child : children ) + { + component.append( child ); + } + return component; + } + + public static Text position( BlockPos pos ) + { + if( pos == null ) return translate( "commands.computercraft.generic.no_position" ); + return translate( "commands.computercraft.generic.position", pos.getX(), pos.getY(), pos.getZ() ); + } + + public static Text bool( boolean value ) + { + return value + ? coloured( translate( "commands.computercraft.generic.yes" ), Formatting.GREEN ) + : coloured( translate( "commands.computercraft.generic.no" ), Formatting.RED ); + } + + public static Text link( Text component, String command, Text toolTip ) + { + Style style = component.getStyle(); + + if( style.getColor() == null ) style.withColor( Formatting.YELLOW ); + style.withClickEvent( new ClickEvent( ClickEvent.Action.RUN_COMMAND, command ) ); + style.setHoverEvent( new HoverEvent( HoverEvent.Action.SHOW_TEXT, toolTip ) ); + + return component; + } + + public static Text header( String text ) + { + return coloured( text, HEADER ); + } +} diff --git a/remappedSrc/dan200/computercraft/shared/command/text/ServerTableFormatter.java b/remappedSrc/dan200/computercraft/shared/command/text/ServerTableFormatter.java new file mode 100644 index 000000000..14ffc2e04 --- /dev/null +++ b/remappedSrc/dan200/computercraft/shared/command/text/ServerTableFormatter.java @@ -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.shared.command.text; + +import net.minecraft.server.command.ServerCommandSource; +import net.minecraft.text.LiteralText; +import net.minecraft.text.Text; +import org.apache.commons.lang3.StringUtils; + +import javax.annotation.Nullable; + +public class ServerTableFormatter implements TableFormatter +{ + private final ServerCommandSource source; + + public ServerTableFormatter( ServerCommandSource source ) + { + this.source = source; + } + + @Override + @Nullable + public Text getPadding( Text component, int width ) + { + int extraWidth = width - getWidth( component ); + if( extraWidth <= 0 ) return null; + return new LiteralText( StringUtils.repeat( ' ', extraWidth ) ); + } + + @Override + public int getColumnPadding() + { + return 1; + } + + @Override + public int getWidth( Text component ) + { + return component.asString().length(); + } + + @Override + public void writeLine( int id, Text component ) + { + source.sendFeedback( component, false ); + } +} diff --git a/remappedSrc/dan200/computercraft/shared/command/text/TableBuilder.java b/remappedSrc/dan200/computercraft/shared/command/text/TableBuilder.java new file mode 100644 index 000000000..1b163f6a7 --- /dev/null +++ b/remappedSrc/dan200/computercraft/shared/command/text/TableBuilder.java @@ -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.shared.command.text; + +import dan200.computercraft.shared.command.CommandUtils; +import dan200.computercraft.shared.network.NetworkHandler; +import dan200.computercraft.shared.network.client.ChatTableClientMessage; +import net.minecraft.server.command.ServerCommandSource; +import net.minecraft.server.network.ServerPlayerEntity; +import net.minecraft.text.Text; + +import javax.annotation.Nonnull; +import javax.annotation.Nullable; +import java.util.ArrayList; +import java.util.List; + +public class TableBuilder +{ + private final int id; + private int columns = -1; + private final Text[] headers; + private final ArrayList rows = new ArrayList<>(); + private int additional; + + public TableBuilder( int id, @Nonnull Text... headers ) + { + if( id < 0 ) throw new IllegalArgumentException( "ID must be positive" ); + this.id = id; + this.headers = headers; + columns = headers.length; + } + + public TableBuilder( int id ) + { + if( id < 0 ) throw new IllegalArgumentException( "ID must be positive" ); + this.id = id; + headers = null; + } + + public TableBuilder( int id, @Nonnull String... headers ) + { + if( id < 0 ) throw new IllegalArgumentException( "ID must be positive" ); + this.id = id; + this.headers = new Text[headers.length]; + columns = headers.length; + + for( int i = 0; i < headers.length; i++ ) this.headers[i] = ChatHelpers.header( headers[i] ); + } + + public void row( @Nonnull Text... row ) + { + if( columns == -1 ) columns = row.length; + if( row.length != columns ) throw new IllegalArgumentException( "Row is the incorrect length" ); + rows.add( row ); + } + + /** + * Get the unique identifier for this table type. + * + * When showing a table within Minecraft, previous instances of this table with + * the same ID will be removed from chat. + * + * @return This table's type. + */ + public int getId() + { + return id; + } + + /** + * Get the number of columns for this table. + * + * This will be the same as {@link #getHeaders()}'s length if it is is non-{@code null}, + * otherwise the length of the first column. + * + * @return The number of columns. + */ + public int getColumns() + { + return columns; + } + + @Nullable + public Text[] getHeaders() + { + return headers; + } + + @Nonnull + public List getRows() + { + return rows; + } + + public int getAdditional() + { + return additional; + } + + public void setAdditional( int additional ) + { + this.additional = additional; + } + + /** + * Trim this table to a given height + * + * @param height The desired height. + */ + public void trim( int height ) + { + if( rows.size() > height ) + { + additional += rows.size() - height - 1; + rows.subList( height - 1, rows.size() ).clear(); + } + } + + public void display( ServerCommandSource source ) + { + if( CommandUtils.isPlayer( source ) ) + { + trim( 18 ); + NetworkHandler.sendToPlayer( (ServerPlayerEntity) source.getEntity(), new ChatTableClientMessage( this ) ); + } + else + { + trim( 100 ); + new ServerTableFormatter( source ).display( this ); + } + } +} diff --git a/remappedSrc/dan200/computercraft/shared/command/text/TableFormatter.java b/remappedSrc/dan200/computercraft/shared/command/text/TableFormatter.java new file mode 100644 index 000000000..f5040fc00 --- /dev/null +++ b/remappedSrc/dan200/computercraft/shared/command/text/TableFormatter.java @@ -0,0 +1,120 @@ +/* + * 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.shared.command.text; + +import net.minecraft.text.LiteralText; +import net.minecraft.text.Text; +import net.minecraft.util.Formatting; +import org.apache.commons.lang3.StringUtils; + +import javax.annotation.Nullable; + +import static dan200.computercraft.shared.command.text.ChatHelpers.coloured; +import static dan200.computercraft.shared.command.text.ChatHelpers.translate; + +public interface TableFormatter +{ + Text SEPARATOR = coloured( "| ", Formatting.GRAY ); + Text HEADER = coloured( "=", Formatting.GRAY ); + + /** + * Get additional padding for the component + * + * @param component The component to pad + * @param width The desired width for the component + * @return The padding for this component, or {@code null} if none is needed. + */ + @Nullable + Text getPadding( Text component, int width ); + + /** + * Get the minimum padding between each column + * + * @return The minimum padding. + */ + int getColumnPadding(); + + int getWidth( Text component ); + + void writeLine( int id, Text component ); + + default int display( TableBuilder table ) + { + if( table.getColumns() <= 0 ) return 0; + + int rowId = table.getId(); + int columns = table.getColumns(); + int[] maxWidths = new int[columns]; + + Text[] headers = table.getHeaders(); + if( headers != null ) + { + for( int i = 0; i < columns; i++ ) maxWidths[i] = getWidth( headers[i] ); + } + + for( Text[] row : table.getRows() ) + { + for( int i = 0; i < row.length; i++ ) + { + int width = getWidth( row[i] ); + if( width > maxWidths[i] ) maxWidths[i] = width; + } + } + + // Add a small amount of padding after each column + { + int padding = getColumnPadding(); + for( int i = 0; i < maxWidths.length - 1; i++ ) maxWidths[i] += padding; + } + + // And compute the total width + int totalWidth = (columns - 1) * getWidth( SEPARATOR ); + for( int x : maxWidths ) totalWidth += x; + + if( headers != null ) + { + LiteralText line = new LiteralText( "" ); + for( int i = 0; i < columns - 1; i++ ) + { + line.append( headers[i] ); + Text padding = getPadding( headers[i], maxWidths[i] ); + if( padding != null ) line.append( padding ); + line.append( SEPARATOR ); + } + line.append( headers[columns - 1] ); + + writeLine( rowId++, line ); + + // Write a separator line. We round the width up rather than down to make + // it a tad prettier. + int rowCharWidth = getWidth( HEADER ); + int rowWidth = totalWidth / rowCharWidth + (totalWidth % rowCharWidth == 0 ? 0 : 1); + writeLine( rowId++, coloured( StringUtils.repeat( HEADER.asString(), rowWidth ), Formatting.GRAY ) ); + } + + for( Text[] row : table.getRows() ) + { + LiteralText line = new LiteralText( "" ); + for( int i = 0; i < columns - 1; i++ ) + { + line.append( row[i] ); + Text padding = getPadding( row[i], maxWidths[i] ); + if( padding != null ) line.append( padding ); + line.append( SEPARATOR ); + } + line.append( row[columns - 1] ); + writeLine( rowId++, line ); + } + + if( table.getAdditional() > 0 ) + { + writeLine( rowId++, coloured( translate( "commands.computercraft.generic.additional_rows", table.getAdditional() ), Formatting.AQUA ) ); + } + + return rowId - table.getId(); + } +} diff --git a/remappedSrc/dan200/computercraft/shared/common/BlockGeneric.java b/remappedSrc/dan200/computercraft/shared/common/BlockGeneric.java new file mode 100644 index 000000000..b380327f5 --- /dev/null +++ b/remappedSrc/dan200/computercraft/shared/common/BlockGeneric.java @@ -0,0 +1,80 @@ +/* + * 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.shared.common; + +import dan200.computercraft.shared.util.NamedBlockEntityType; +import net.minecraft.block.Block; +import net.minecraft.block.BlockEntityProvider; +import net.minecraft.block.BlockState; +import net.minecraft.block.entity.BlockEntity; +import net.minecraft.block.entity.BlockEntityType; +import net.minecraft.entity.player.PlayerEntity; +import net.minecraft.util.Hand; +import net.minecraft.util.hit.BlockHitResult; +import net.minecraft.util.math.BlockPos; +import net.minecraft.world.BlockView; +import net.minecraft.world.World; + +import javax.annotation.Nonnull; +import javax.annotation.Nullable; +import java.util.Random; + +public abstract class BlockGeneric extends Block implements BlockEntityProvider +{ + private final BlockEntityType type; + + public BlockGeneric( Settings settings, NamedBlockEntityType type ) + { + super( settings ); + this.type = type; + type.setBlock( this ); + } + + @Override + @Deprecated + public final void onStateReplaced( @Nonnull BlockState block, @Nonnull World world, @Nonnull BlockPos pos, BlockState replace, boolean bool ) + { + if( block.getBlock() == replace.getBlock() ) return; + + BlockEntity tile = world.getBlockEntity( pos ); + super.onStateReplaced( block, world, pos, replace, bool ); + world.removeBlockEntity( pos ); + if( tile instanceof TileGeneric ) ((TileGeneric) tile).destroy(); + } + + @Override + @Deprecated + public final boolean onUse( BlockState state, World world, BlockPos pos, PlayerEntity player, Hand hand, BlockHitResult hit ) + { + BlockEntity tile = world.getBlockEntity( pos ); + return tile instanceof TileGeneric && ((TileGeneric) tile).onActivate( player, hand, hit ); + } + + @Override + @Deprecated + public final void neighborUpdate( BlockState state, World world, BlockPos pos, Block neighbourBlock, BlockPos neighbourPos, boolean flag ) + { + super.neighborUpdate( state, world, pos, neighbourBlock, neighbourPos, flag ); + BlockEntity tile = world.getBlockEntity( pos ); + if( tile instanceof TileGeneric ) ((TileGeneric) tile).onNeighbourChange( neighbourPos ); + } + + @Override + @Deprecated + public void scheduledTick( BlockState state, World world, BlockPos pos, Random rand ) + { + BlockEntity te = world.getBlockEntity( pos ); + if( te instanceof TileGeneric ) ((TileGeneric) te).blockTick(); + } + + @Nullable + @Override + public BlockEntity createBlockEntity( BlockView blockView ) + { + return type.instantiate(); + } +} diff --git a/remappedSrc/dan200/computercraft/shared/common/ClientTerminal.java b/remappedSrc/dan200/computercraft/shared/common/ClientTerminal.java new file mode 100644 index 000000000..aec075e53 --- /dev/null +++ b/remappedSrc/dan200/computercraft/shared/common/ClientTerminal.java @@ -0,0 +1,86 @@ +/* + * 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.shared.common; + +import dan200.computercraft.core.terminal.Terminal; +import net.minecraft.nbt.CompoundTag; + +public class ClientTerminal implements ITerminal +{ + private boolean m_colour; + private Terminal m_terminal; + private boolean m_terminalChanged; + + public ClientTerminal( boolean colour ) + { + m_colour = colour; + m_terminal = null; + m_terminalChanged = false; + } + + public boolean pollTerminalChanged() + { + boolean changed = m_terminalChanged; + m_terminalChanged = false; + + Terminal terminal = m_terminal; + if( terminal != null ) terminal.clearChanged(); + + return changed; + } + + // ITerminal implementation + + @Override + public Terminal getTerminal() + { + return m_terminal; + } + + @Override + public boolean isColour() + { + return m_colour; + } + + public void readDescription( CompoundTag nbt ) + { + m_colour = nbt.getBoolean( "colour" ); + if( nbt.contains( "terminal" ) ) + { + CompoundTag terminal = nbt.getCompound( "terminal" ); + resizeTerminal( terminal.getInt( "term_width" ), terminal.getInt( "term_height" ) ); + m_terminal.readFromNBT( terminal ); + } + else + { + deleteTerminal(); + } + } + + private void resizeTerminal( int width, int height ) + { + if( m_terminal == null ) + { + m_terminal = new Terminal( width, height, () -> m_terminalChanged = true ); + m_terminalChanged = true; + } + else + { + m_terminal.resize( width, height ); + } + } + + private void deleteTerminal() + { + if( m_terminal != null ) + { + m_terminal = null; + m_terminalChanged = true; + } + } +} diff --git a/remappedSrc/dan200/computercraft/shared/common/ColourableRecipe.java b/remappedSrc/dan200/computercraft/shared/common/ColourableRecipe.java new file mode 100644 index 000000000..e27bac54f --- /dev/null +++ b/remappedSrc/dan200/computercraft/shared/common/ColourableRecipe.java @@ -0,0 +1,104 @@ +/* + * 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.shared.common; + +import dan200.computercraft.shared.util.Colour; +import dan200.computercraft.shared.util.ColourTracker; +import dan200.computercraft.shared.util.ColourUtils; +import net.minecraft.inventory.CraftingInventory; +import net.minecraft.item.ItemStack; +import net.minecraft.recipe.RecipeSerializer; +import net.minecraft.recipe.SpecialCraftingRecipe; +import net.minecraft.recipe.SpecialRecipeSerializer; +import net.minecraft.util.DyeColor; +import net.minecraft.util.Identifier; +import net.minecraft.world.World; + +import javax.annotation.Nonnull; + +public class ColourableRecipe extends SpecialCraftingRecipe +{ + public ColourableRecipe( Identifier id ) + { + super( id ); + } + + @Override + public boolean matches( @Nonnull CraftingInventory inv, @Nonnull World world ) + { + boolean hasColourable = false; + boolean hasDye = false; + for( int i = 0; i < inv.size(); i++ ) + { + ItemStack stack = inv.getStack( i ); + if( stack.isEmpty() ) continue; + + if( stack.getItem() instanceof IColouredItem ) + { + if( hasColourable ) return false; + hasColourable = true; + } + else if( ColourUtils.getStackColour( stack ) != null ) + { + hasDye = true; + } + else + { + return false; + } + } + + return hasColourable && hasDye; + } + + @Nonnull + @Override + public ItemStack craft( @Nonnull CraftingInventory inv ) + { + ItemStack colourable = ItemStack.EMPTY; + + ColourTracker tracker = new ColourTracker(); + + for( int i = 0; i < inv.size(); i++ ) + { + ItemStack stack = inv.getStack( i ); + + if( stack.isEmpty() ) continue; + + if( stack.getItem() instanceof IColouredItem ) + { + colourable = stack; + } + else + { + DyeColor dye = ColourUtils.getStackColour( stack ); + if( dye == null ) continue; + + Colour colour = Colour.fromInt( 15 - dye.getId() ); + tracker.addColour( colour.getR(), colour.getG(), colour.getB() ); + } + } + + if( colourable.isEmpty() ) return ItemStack.EMPTY; + return ((IColouredItem) colourable.getItem()).withColour( colourable, tracker.getColour() ); + } + + @Override + public boolean fits( int x, int y ) + { + return x >= 2 && y >= 2; + } + + @Override + @Nonnull + public RecipeSerializer getSerializer() + { + return SERIALIZER; + } + + public static final RecipeSerializer SERIALIZER = new SpecialRecipeSerializer<>( ColourableRecipe::new ); +} diff --git a/remappedSrc/dan200/computercraft/shared/common/ContainerHeldItem.java b/remappedSrc/dan200/computercraft/shared/common/ContainerHeldItem.java new file mode 100644 index 000000000..b2e6b6baf --- /dev/null +++ b/remappedSrc/dan200/computercraft/shared/common/ContainerHeldItem.java @@ -0,0 +1,43 @@ +/* + * 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.shared.common; + +import dan200.computercraft.shared.util.InventoryUtil; +import net.minecraft.entity.player.PlayerEntity; +import net.minecraft.item.ItemStack; +import net.minecraft.screen.ScreenHandler; +import net.minecraft.util.Hand; + +import javax.annotation.Nonnull; + +public class ContainerHeldItem extends ScreenHandler +{ + private final ItemStack m_stack; + private final Hand m_hand; + + public ContainerHeldItem( int id, PlayerEntity player, Hand hand ) + { + super( null, id ); + m_hand = hand; + m_stack = InventoryUtil.copyItem( player.getStackInHand( hand ) ); + } + + @Nonnull + public ItemStack getStack() + { + return m_stack; + } + + @Override + public boolean canUse( @Nonnull PlayerEntity player ) + { + if( !player.isAlive() ) return false; + + ItemStack stack = player.getStackInHand( m_hand ); + return stack == m_stack || !stack.isEmpty() && !m_stack.isEmpty() && stack.getItem() == m_stack.getItem(); + } +} diff --git a/remappedSrc/dan200/computercraft/shared/common/DefaultBundledRedstoneProvider.java b/remappedSrc/dan200/computercraft/shared/common/DefaultBundledRedstoneProvider.java new file mode 100644 index 000000000..bae001cb8 --- /dev/null +++ b/remappedSrc/dan200/computercraft/shared/common/DefaultBundledRedstoneProvider.java @@ -0,0 +1,38 @@ +/* + * 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.shared.common; + +import dan200.computercraft.api.redstone.IBundledRedstoneProvider; +import net.minecraft.block.Block; +import net.minecraft.util.math.BlockPos; +import net.minecraft.util.math.Direction; +import net.minecraft.world.World; + +import javax.annotation.Nonnull; + +public class DefaultBundledRedstoneProvider implements IBundledRedstoneProvider +{ + @Override + public int getBundledRedstoneOutput( @Nonnull World world, @Nonnull BlockPos pos, @Nonnull Direction side ) + { + return getDefaultBundledRedstoneOutput( world, pos, side ); + } + + public static int getDefaultBundledRedstoneOutput( World world, BlockPos pos, Direction side ) + { + Block block = world.getBlockState( pos ).getBlock(); + if( block instanceof IBundledRedstoneBlock ) + { + IBundledRedstoneBlock generic = (IBundledRedstoneBlock) block; + if( generic.getBundledRedstoneConnectivity( world, pos, side ) ) + { + return generic.getBundledRedstoneOutput( world, pos, side ); + } + } + return -1; + } +} diff --git a/remappedSrc/dan200/computercraft/shared/common/IBundledRedstoneBlock.java b/remappedSrc/dan200/computercraft/shared/common/IBundledRedstoneBlock.java new file mode 100644 index 000000000..d227436fc --- /dev/null +++ b/remappedSrc/dan200/computercraft/shared/common/IBundledRedstoneBlock.java @@ -0,0 +1,18 @@ +/* + * 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.shared.common; + +import net.minecraft.util.math.BlockPos; +import net.minecraft.util.math.Direction; +import net.minecraft.world.World; + +public interface IBundledRedstoneBlock +{ + boolean getBundledRedstoneConnectivity( World world, BlockPos pos, Direction side ); + + int getBundledRedstoneOutput( World world, BlockPos pos, Direction side ); +} diff --git a/remappedSrc/dan200/computercraft/shared/common/IColouredItem.java b/remappedSrc/dan200/computercraft/shared/common/IColouredItem.java new file mode 100644 index 000000000..bdc1c468c --- /dev/null +++ b/remappedSrc/dan200/computercraft/shared/common/IColouredItem.java @@ -0,0 +1,46 @@ +/* + * 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.shared.common; + +import net.minecraft.item.ItemStack; +import net.minecraft.nbt.CompoundTag; + +public interface IColouredItem +{ + String NBT_COLOUR = "Color"; + + default int getColour( ItemStack stack ) + { + return getColourBasic( stack ); + } + + default ItemStack withColour( ItemStack stack, int colour ) + { + ItemStack copy = stack.copy(); + setColourBasic( copy, colour ); + return copy; + } + + static int getColourBasic( ItemStack stack ) + { + CompoundTag tag = stack.getTag(); + return tag != null && tag.contains( NBT_COLOUR ) ? tag.getInt( NBT_COLOUR ) : -1; + } + + static void setColourBasic( ItemStack stack, int colour ) + { + if( colour == -1 ) + { + CompoundTag tag = stack.getTag(); + if( tag != null ) tag.remove( NBT_COLOUR ); + } + else + { + stack.getOrCreateTag().putInt( NBT_COLOUR, colour ); + } + } +} diff --git a/remappedSrc/dan200/computercraft/shared/common/ITerminal.java b/remappedSrc/dan200/computercraft/shared/common/ITerminal.java new file mode 100644 index 000000000..1d46fa271 --- /dev/null +++ b/remappedSrc/dan200/computercraft/shared/common/ITerminal.java @@ -0,0 +1,16 @@ +/* + * 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.shared.common; + +import dan200.computercraft.core.terminal.Terminal; + +public interface ITerminal +{ + Terminal getTerminal(); + + boolean isColour(); +} diff --git a/remappedSrc/dan200/computercraft/shared/common/ServerTerminal.java b/remappedSrc/dan200/computercraft/shared/common/ServerTerminal.java new file mode 100644 index 000000000..c40bbbc75 --- /dev/null +++ b/remappedSrc/dan200/computercraft/shared/common/ServerTerminal.java @@ -0,0 +1,101 @@ +/* + * 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.shared.common; + +import dan200.computercraft.core.terminal.Terminal; +import net.minecraft.nbt.CompoundTag; + +import java.util.concurrent.atomic.AtomicBoolean; + +public class ServerTerminal implements ITerminal +{ + private final boolean m_colour; + private Terminal m_terminal; + private final AtomicBoolean m_terminalChanged = new AtomicBoolean( false ); + private boolean m_terminalChangedLastFrame = false; + + public ServerTerminal( boolean colour ) + { + m_colour = colour; + m_terminal = null; + } + + public ServerTerminal( boolean colour, int terminalWidth, int terminalHeight ) + { + m_colour = colour; + m_terminal = new Terminal( terminalWidth, terminalHeight, this::markTerminalChanged ); + } + + protected void resize( int width, int height ) + { + if( m_terminal == null ) + { + m_terminal = new Terminal( width, height, this::markTerminalChanged ); + markTerminalChanged(); + } + else + { + m_terminal.resize( width, height ); + } + } + + public void delete() + { + if( m_terminal != null ) + { + m_terminal = null; + markTerminalChanged(); + } + } + + protected void markTerminalChanged() + { + m_terminalChanged.set( true ); + } + + public void update() + { + Terminal terminal = m_terminal; + if( terminal != null ) terminal.clearChanged(); + + m_terminalChangedLastFrame = m_terminalChanged.getAndSet( false ); + } + + public boolean hasTerminalChanged() + { + return m_terminalChangedLastFrame; + } + + // ITerminal implementation + + @Override + public Terminal getTerminal() + { + return m_terminal; + } + + @Override + public boolean isColour() + { + return m_colour; + } + + // Networking stuff + + public void writeDescription( CompoundTag nbt ) + { + nbt.putBoolean( "colour", m_colour ); + if( m_terminal != null ) + { + CompoundTag terminal = new CompoundTag(); + terminal.putInt( "term_width", m_terminal.getWidth() ); + terminal.putInt( "term_height", m_terminal.getHeight() ); + m_terminal.writeToNBT( terminal ); + nbt.put( "terminal", terminal ); + } + } +} diff --git a/remappedSrc/dan200/computercraft/shared/common/TileGeneric.java b/remappedSrc/dan200/computercraft/shared/common/TileGeneric.java new file mode 100644 index 000000000..7fb9179bd --- /dev/null +++ b/remappedSrc/dan200/computercraft/shared/common/TileGeneric.java @@ -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.shared.common; + +import net.fabricmc.fabric.api.block.entity.BlockEntityClientSerializable; +import net.minecraft.block.BlockState; +import net.minecraft.block.entity.BlockEntity; +import net.minecraft.block.entity.BlockEntityType; +import net.minecraft.entity.player.PlayerEntity; +import net.minecraft.nbt.CompoundTag; +import net.minecraft.util.Hand; +import net.minecraft.util.hit.BlockHitResult; +import net.minecraft.util.math.BlockPos; + +import javax.annotation.Nonnull; + +public abstract class TileGeneric extends BlockEntity implements BlockEntityClientSerializable +{ + public TileGeneric( BlockEntityType type ) + { + super( type ); + } + + public void destroy() + { + } + + public final void updateBlock() + { + markDirty(); + BlockPos pos = getPos(); + BlockState state = getCachedState(); + getWorld().updateListeners( pos, state, state, 3 ); + } + + public boolean onActivate( PlayerEntity player, Hand hand, BlockHitResult hit ) + { + return false; + } + + public void onNeighbourChange( @Nonnull BlockPos neighbour ) + { + } + + public void onNeighbourTileEntityChange( @Nonnull BlockPos neighbour ) + { + } + + protected void blockTick() + { + } + + protected double getInteractRange( PlayerEntity player ) + { + return 8.0; + } + + public boolean isUsable( PlayerEntity player, boolean ignoreRange ) + { + if( player == null || !player.isAlive() || getWorld().getBlockEntity( getPos() ) != this ) return false; + if( ignoreRange ) return true; + + double range = getInteractRange( player ); + BlockPos pos = getPos(); + return player.getEntityWorld() == getWorld() && + player.squaredDistanceTo( pos.getX() + 0.5, pos.getY() + 0.5, pos.getZ() + 0.5 ) <= range * range; + } + + protected void writeDescription( @Nonnull CompoundTag nbt ) + { + } + + protected void readDescription( @Nonnull CompoundTag nbt ) + { + } + + @Override + public final CompoundTag toClientTag( CompoundTag tag ) + { + writeDescription( tag ); + return tag; + } + + @Override + public final void fromClientTag( CompoundTag tag ) + { + readDescription( tag ); + } +} diff --git a/remappedSrc/dan200/computercraft/shared/computer/apis/CommandAPI.java b/remappedSrc/dan200/computercraft/shared/computer/apis/CommandAPI.java new file mode 100644 index 000000000..979d12586 --- /dev/null +++ b/remappedSrc/dan200/computercraft/shared/computer/apis/CommandAPI.java @@ -0,0 +1,270 @@ +/* + * 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.shared.computer.apis; + +import com.google.common.collect.ImmutableMap; +import com.mojang.brigadier.tree.CommandNode; +import com.mojang.brigadier.tree.LiteralCommandNode; +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.shared.computer.blocks.TileCommandComputer; +import dan200.computercraft.shared.util.NBTUtil; +import net.minecraft.block.Block; +import net.minecraft.block.BlockState; +import net.minecraft.block.entity.BlockEntity; +import net.minecraft.nbt.CompoundTag; +import net.minecraft.server.MinecraftServer; +import net.minecraft.server.command.CommandManager; +import net.minecraft.server.command.ServerCommandSource; +import net.minecraft.server.world.ServerWorld; +import net.minecraft.state.property.Property; +import net.minecraft.util.math.BlockPos; +import net.minecraft.util.registry.Registry; +import net.minecraft.world.World; + +import javax.annotation.Nonnull; +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; + +import static dan200.computercraft.core.apis.ArgumentHelper.*; + +public class CommandAPI implements ILuaAPI +{ + private TileCommandComputer m_computer; + + public CommandAPI( TileCommandComputer computer ) + { + m_computer = computer; + } + + // ILuaAPI implementation + + @Override + public String[] getNames() + { + return new String[] { + "commands" + }; + } + + @Nonnull + @Override + public String[] getMethodNames() + { + return new String[] { + "exec", + "execAsync", + "list", + "getBlockPosition", + "getBlockInfos", + "getBlockInfo" + }; + } + + private static Map createOutput( String output ) + { + return Collections.singletonMap( 1, output ); + } + + private Object[] doCommand( String command ) + { + MinecraftServer server = m_computer.getWorld().getServer(); + if( server == null || !server.areCommandBlocksEnabled() ) + { + return new Object[] { false, createOutput( "Command blocks disabled by server" ) }; + } + + CommandManager commandManager = server.getCommandManager(); + TileCommandComputer.CommandReceiver receiver = m_computer.getReceiver(); + try + { + receiver.clearOutput(); + int result = commandManager.execute( m_computer.getSource(), command ); + return new Object[] { result > 0, receiver.copyOutput() }; + } + catch( Throwable t ) + { + if( ComputerCraft.logPeripheralErrors ) ComputerCraft.log.error( "Error running command.", t ); + return new Object[] { false, createOutput( "Java Exception Thrown: " + t ) }; + } + } + + private World getWorldFromID(@Nonnull Object[] arguments, int index) throws LuaException { + World world = null; + final int dim = optInt(arguments, index, m_computer.getWorld().dimension.getType().getRawId()); + for(World w : m_computer.getWorld().getServer().getWorlds()) { + if (w.getDimension().getType().getRawId() == dim) { + world = w; + break; + } + } + if (world == null) { + throw new LuaException( "Invalid dimension ID" ); + } + return world; + } + + private static Object getBlockInfo( World world, BlockPos pos ) + { + // Get the details of the block + BlockState state = world.getBlockState( pos ); + Block block = state.getBlock(); + int id = world.dimension.getType().getRawId(); + + Map table = new HashMap<>(); + table.put( "name", Registry.BLOCK.getId( block ).toString() ); + table.put( "dimension", id); + + Map stateTable = new HashMap<>(); + for( ImmutableMap.Entry, Comparable> entry : state.getEntries().entrySet() ) + { + Property property = entry.getKey(); + stateTable.put( property.getName(), getPropertyValue( property, entry.getValue() ) ); + } + table.put( "state", stateTable ); + + BlockEntity tile = world.getBlockEntity( pos ); + if( tile != null ) table.put( "nbt", NBTUtil.toLua( tile.toTag( new CompoundTag() ) ) ); + + return table; + } + + @SuppressWarnings( { "unchecked", "rawtypes" } ) + private static Object getPropertyValue( Property property, Comparable value ) + { + if( value instanceof String || value instanceof Number || value instanceof Boolean ) return value; + return property.name( value ); + } + + @Override + public Object[] callMethod( @Nonnull ILuaContext context, int method, @Nonnull Object[] arguments ) throws LuaException, InterruptedException + { + switch( method ) + { + case 0: // exec + { + final String command = getString( arguments, 0 ); + return context.executeMainThreadTask( () -> doCommand( command ) ); + } + case 1: // execAsync + { + final String command = getString( arguments, 0 ); + long taskID = context.issueMainThreadTask( () -> doCommand( command ) ); + return new Object[] { taskID }; + } + case 2: + // list + return context.executeMainThreadTask( () -> + { + MinecraftServer server = m_computer.getWorld().getServer(); + + if( server == null ) return new Object[] { Collections.emptyMap() }; + CommandNode node = server.getCommandManager().getDispatcher().getRoot(); + for( int j = 0; j < arguments.length; j++ ) + { + String name = getString( arguments, j ); + node = node.getChild( name ); + if( !(node instanceof LiteralCommandNode) ) return new Object[] { Collections.emptyMap() }; + } + + int i = 1; + Map result = new HashMap<>(); + for( CommandNode child : node.getChildren() ) + { + if( child instanceof LiteralCommandNode ) result.put( i++, child.getName() ); + } + return new Object[] { result }; + } ); + case 3: // getBlockPosition + { + // This is probably safe to do on the Lua thread. Probably. + BlockPos pos = m_computer.getPos(); + return new Object[] { pos.getX(), pos.getY(), pos.getZ() }; + } + case 4: + { + // getBlockInfos + final int minX = getInt( arguments, 0 ); + final int minY = getInt( arguments, 1 ); + final int minZ = getInt( arguments, 2 ); + final int maxX = getInt( arguments, 3 ); + final int maxY = getInt( arguments, 4 ); + final int maxZ = getInt( arguments, 5 ); + + return context.executeMainThreadTask( () -> + { + // Get the details of the block + World world = getWorldFromID(arguments, 6); + + BlockPos min = new BlockPos( + Math.min( minX, maxX ), + Math.min( minY, maxY ), + Math.min( minZ, maxZ ) + ); + BlockPos max = new BlockPos( + Math.max( minX, maxX ), + Math.max( minY, maxY ), + Math.max( minZ, maxZ ) + ); + if( !World.isValid( min ) || !World.isValid( max ) ) + { + throw new LuaException( "Co-ordinates out of range" ); + } + if( (max.getX() - min.getX() + 1) * (max.getY() - min.getY() + 1) * (max.getZ() - min.getZ() + 1) > 4096 ) + { + throw new LuaException( "Too many blocks" ); + } + int i = 1; + Map results = new HashMap<>(); + for( int y = min.getY(); y <= max.getY(); y++ ) + { + for( int z = min.getZ(); z <= max.getZ(); z++ ) + { + for( int x = min.getX(); x <= max.getX(); x++ ) + { + BlockPos pos = new BlockPos( x, y, z ); + results.put( i++, getBlockInfo( world, pos ) ); + } + } + } + return new Object[] { results }; + } ); + } + case 5: + { + // getBlockInfo + final int x = getInt( arguments, 0 ); + final int y = getInt( arguments, 1 ); + final int z = getInt( arguments, 2 ); + + return context.executeMainThreadTask( () -> + { + // Get the details of the block + World world = getWorldFromID(arguments, 3); + + BlockPos position = new BlockPos( x, y, z ); + if( World.isValid( position ) ) + { + return new Object[] { getBlockInfo( world, position ) }; + } + else + { + throw new LuaException( "co-ordinates out of range" ); + } + } ); + } + default: + { + return null; + } + } + } +} diff --git a/remappedSrc/dan200/computercraft/shared/computer/blocks/BlockComputer.java b/remappedSrc/dan200/computercraft/shared/computer/blocks/BlockComputer.java new file mode 100644 index 000000000..8b2b60816 --- /dev/null +++ b/remappedSrc/dan200/computercraft/shared/computer/blocks/BlockComputer.java @@ -0,0 +1,59 @@ +/* + * 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.shared.computer.blocks; + +import dan200.computercraft.shared.computer.core.ComputerFamily; +import dan200.computercraft.shared.computer.core.ComputerState; +import dan200.computercraft.shared.computer.items.ComputerItemFactory; +import dan200.computercraft.shared.util.NamedBlockEntityType; +import net.minecraft.block.Block; +import net.minecraft.block.BlockState; +import net.minecraft.item.ItemPlacementContext; +import net.minecraft.item.ItemStack; +import net.minecraft.state.StateManager; +import net.minecraft.state.property.DirectionProperty; +import net.minecraft.state.property.EnumProperty; +import net.minecraft.state.property.Properties; +import net.minecraft.util.math.Direction; + +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class BlockComputer extends BlockComputerBase +{ + public static final EnumProperty STATE = EnumProperty.of( "state", ComputerState.class ); + public static final DirectionProperty FACING = Properties.HORIZONTAL_FACING; + + public BlockComputer( Settings settings, ComputerFamily family, NamedBlockEntityType type ) + { + super( settings, family, type ); + setDefaultState( getDefaultState() + .with( FACING, Direction.NORTH ) + .with( STATE, ComputerState.OFF ) + ); + } + + @Override + protected void appendProperties( StateManager.Builder builder ) + { + builder.add( FACING, STATE ); + } + + @Nullable + @Override + public BlockState getPlacementState( ItemPlacementContext placement ) + { + return getDefaultState().with( FACING, placement.getPlayerFacing().getOpposite() ); + } + + @Nonnull + @Override + protected ItemStack getItem( TileComputerBase tile ) + { + return tile instanceof TileComputer ? ComputerItemFactory.create( (TileComputer) tile ) : ItemStack.EMPTY; + } +} diff --git a/remappedSrc/dan200/computercraft/shared/computer/blocks/BlockComputerBase.java b/remappedSrc/dan200/computercraft/shared/computer/blocks/BlockComputerBase.java new file mode 100644 index 000000000..dd7cf49ed --- /dev/null +++ b/remappedSrc/dan200/computercraft/shared/computer/blocks/BlockComputerBase.java @@ -0,0 +1,154 @@ +/* + * 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.shared.computer.blocks; + +import dan200.computercraft.ComputerCraft; +import dan200.computercraft.core.computer.ComputerSide; +import dan200.computercraft.shared.common.BlockGeneric; +import dan200.computercraft.shared.common.IBundledRedstoneBlock; +import dan200.computercraft.shared.computer.core.ComputerFamily; +import dan200.computercraft.shared.computer.core.ServerComputer; +import dan200.computercraft.shared.computer.items.IComputerItem; +import dan200.computercraft.shared.util.NamedBlockEntityType; +import net.minecraft.block.BlockState; +import net.minecraft.block.entity.BlockEntity; +import net.minecraft.entity.LivingEntity; +import net.minecraft.item.ItemStack; +import net.minecraft.loot.context.LootContext; +import net.minecraft.loot.context.LootContextParameters; +import net.minecraft.util.Identifier; +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 java.util.List; + +public abstract class BlockComputerBase extends BlockGeneric implements IBundledRedstoneBlock +{ + private static final Identifier COMPUTER_DROP = new Identifier( ComputerCraft.MOD_ID, "computer" ); + + private final ComputerFamily family; + + protected BlockComputerBase( Settings settings, ComputerFamily family, NamedBlockEntityType type ) + { + super( settings, type ); + this.family = family; + } + + @Override + @Deprecated + public void onBlockAdded( BlockState state, World world, BlockPos pos, BlockState oldState, boolean flag ) + { + super.onBlockAdded( state, world, pos, oldState, flag ); + + BlockEntity tile = world.getBlockEntity( pos ); + if( tile instanceof TileComputerBase ) ((TileComputerBase) tile).updateInput(); + } + + @Override + @Deprecated + public boolean emitsRedstonePower( BlockState state ) + { + return true; + } + + @Override + @Deprecated + public int getStrongRedstonePower( BlockState state, BlockView world, BlockPos pos, Direction incomingSide ) + { + BlockEntity entity = world.getBlockEntity( pos ); + if( !(entity instanceof TileComputerBase) ) return 0; + + TileComputerBase computerEntity = (TileComputerBase) entity; + ServerComputer computer = computerEntity.getServerComputer(); + if( computer == null ) return 0; + + ComputerSide localSide = computerEntity.remapToLocalSide( incomingSide.getOpposite() ); + return computer.getRedstoneOutput( localSide ); + } + + @Nonnull + protected abstract ItemStack getItem( TileComputerBase tile ); + + public ComputerFamily getFamily() + { + return family; + } + + @Override + @Deprecated + public int getWeakRedstonePower( BlockState state, BlockView world, BlockPos pos, Direction incomingSide ) + { + return getStrongRedstonePower( state, world, pos, incomingSide ); + } + + @Override + public boolean getBundledRedstoneConnectivity( World world, BlockPos pos, Direction side ) + { + return true; + } + + @Override + public int getBundledRedstoneOutput( World world, BlockPos pos, Direction side ) + { + BlockEntity entity = world.getBlockEntity( pos ); + if( !(entity instanceof TileComputerBase) ) return 0; + + TileComputerBase computerEntity = (TileComputerBase) entity; + ServerComputer computer = computerEntity.getServerComputer(); + if( computer == null ) return 0; + + ComputerSide localSide = computerEntity.remapToLocalSide( side ); + return computer.getBundledRedstoneOutput( localSide ); + } + + @Nonnull + @Override + public ItemStack getPickStack( BlockView world, BlockPos pos, BlockState state ) + { + BlockEntity tile = world.getBlockEntity( pos ); + if( tile instanceof TileComputerBase ) + { + ItemStack result = getItem( (TileComputerBase) tile ); + if( !result.isEmpty() ) return result; + } + + return super.getPickStack( world, pos, state ); + } + + @Override + @Deprecated + public List getDroppedStacks( BlockState state, LootContext.Builder builder ) + { + // TODO: Find a way of doing creative block drops + builder.putDrop( COMPUTER_DROP, ( context, consumer ) -> { + BlockEntity tile = context.get( LootContextParameters.BLOCK_ENTITY ); + if( tile instanceof TileComputerBase ) consumer.accept( getItem( (TileComputerBase) tile ) ); + } ); + return super.getDroppedStacks( state, builder ); + } + + @Override + public void onPlaced( World world, BlockPos pos, BlockState state, LivingEntity placer, ItemStack stack ) + { + super.onPlaced( world, pos, state, placer, stack ); + + BlockEntity tile = world.getBlockEntity( pos ); + if( !world.isClient && tile instanceof IComputerTile && stack.getItem() instanceof IComputerItem ) + { + IComputerTile computer = (IComputerTile) tile; + IComputerItem item = (IComputerItem) stack.getItem(); + + int id = item.getComputerID( stack ); + if( id != -1 ) computer.setComputerID( id ); + + String label = item.getLabel( stack ); + if( label != null ) computer.setLabel( label ); + } + } +} diff --git a/remappedSrc/dan200/computercraft/shared/computer/blocks/ComputerPeripheral.java b/remappedSrc/dan200/computercraft/shared/computer/blocks/ComputerPeripheral.java new file mode 100644 index 000000000..0149ba0b3 --- /dev/null +++ b/remappedSrc/dan200/computercraft/shared/computer/blocks/ComputerPeripheral.java @@ -0,0 +1,86 @@ +/* + * 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.shared.computer.blocks; + +import dan200.computercraft.api.lua.ILuaContext; +import dan200.computercraft.api.peripheral.IComputerAccess; +import dan200.computercraft.api.peripheral.IPeripheral; + +import javax.annotation.Nonnull; + +public class ComputerPeripheral implements IPeripheral +{ + private final String m_type; + private final ComputerProxy m_computer; + + public ComputerPeripheral( String type, ComputerProxy computer ) + { + m_type = type; + m_computer = computer; + } + + // IPeripheral implementation + + @Nonnull + @Override + public String getType() + { + return m_type; + } + + @Nonnull + @Override + public String[] getMethodNames() + { + return new String[] { + "turnOn", + "shutdown", + "reboot", + "getID", + "isOn", + "getLabel", + }; + } + + @Override + public Object[] callMethod( @Nonnull IComputerAccess computer, @Nonnull ILuaContext context, int method, @Nonnull Object[] arguments ) + { + switch( method ) + { + case 0: // turnOn + m_computer.turnOn(); + return null; + case 1: // shutdown + m_computer.shutdown(); + return null; + case 2: // reboot + m_computer.reboot(); + return null; + case 3: // getID + return new Object[] { m_computer.assignID() }; + case 4: // isOn + return new Object[] { m_computer.isOn() }; + case 5: // getLabel + return new Object[] { m_computer.getLabel() }; + default: + return null; + } + } + + @Override + public boolean equals( IPeripheral other ) + { + return other != null && other.getClass() == getClass(); + } + + @Nonnull + @Override + public Object getTarget() + { + return m_computer.getTile(); + } +} diff --git a/remappedSrc/dan200/computercraft/shared/computer/blocks/ComputerProxy.java b/remappedSrc/dan200/computercraft/shared/computer/blocks/ComputerProxy.java new file mode 100644 index 000000000..7990f24ca --- /dev/null +++ b/remappedSrc/dan200/computercraft/shared/computer/blocks/ComputerProxy.java @@ -0,0 +1,80 @@ +/* + * 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.shared.computer.blocks; + +import dan200.computercraft.shared.computer.core.IComputer; +import dan200.computercraft.shared.computer.core.ServerComputer; + +/** + * A proxy object for computer objects, delegating to {@link IComputer} or {@link TileComputer} where appropriate. + */ +public abstract class ComputerProxy +{ + protected abstract TileComputerBase getTile(); + + public void turnOn() + { + TileComputerBase tile = getTile(); + ServerComputer computer = tile.getServerComputer(); + if( computer == null ) + { + tile.m_startOn = true; + } + else + { + computer.turnOn(); + } + } + + public void shutdown() + { + TileComputerBase tile = getTile(); + ServerComputer computer = tile.getServerComputer(); + if( computer == null ) + { + tile.m_startOn = false; + } + else + { + computer.shutdown(); + } + } + + public void reboot() + { + TileComputerBase tile = getTile(); + ServerComputer computer = tile.getServerComputer(); + if( computer == null ) + { + tile.m_startOn = true; + } + else + { + computer.reboot(); + } + } + + public int assignID() + { + TileComputerBase tile = getTile(); + ServerComputer computer = tile.getServerComputer(); + return computer == null ? tile.getComputerID() : computer.getID(); + } + + public boolean isOn() + { + ServerComputer computer = getTile().getServerComputer(); + return computer != null && computer.isOn(); + } + + public String getLabel() + { + TileComputerBase tile = getTile(); + ServerComputer computer = tile.getServerComputer(); + return computer == null ? tile.getLabel() : computer.getLabel(); + } +} diff --git a/remappedSrc/dan200/computercraft/shared/computer/blocks/IComputerTile.java b/remappedSrc/dan200/computercraft/shared/computer/blocks/IComputerTile.java new file mode 100644 index 000000000..868fdceb3 --- /dev/null +++ b/remappedSrc/dan200/computercraft/shared/computer/blocks/IComputerTile.java @@ -0,0 +1,22 @@ +/* + * 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.shared.computer.blocks; + +import dan200.computercraft.shared.computer.core.ComputerFamily; + +public interface IComputerTile +{ + int getComputerID(); + + void setComputerID( int id ); + + String getLabel(); + + void setLabel( String label ); + + ComputerFamily getFamily(); +} diff --git a/remappedSrc/dan200/computercraft/shared/computer/blocks/TileCommandComputer.java b/remappedSrc/dan200/computercraft/shared/computer/blocks/TileCommandComputer.java new file mode 100644 index 000000000..2a6219d90 --- /dev/null +++ b/remappedSrc/dan200/computercraft/shared/computer/blocks/TileCommandComputer.java @@ -0,0 +1,140 @@ +/* + * 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.shared.computer.blocks; + +import dan200.computercraft.ComputerCraft; +import dan200.computercraft.shared.computer.apis.CommandAPI; +import dan200.computercraft.shared.computer.core.ComputerFamily; +import dan200.computercraft.shared.computer.core.ServerComputer; +import dan200.computercraft.shared.util.NamedBlockEntityType; +import net.minecraft.block.entity.BlockEntityType; +import net.minecraft.entity.player.PlayerEntity; +import net.minecraft.server.MinecraftServer; +import net.minecraft.server.command.CommandOutput; +import net.minecraft.server.command.ServerCommandSource; +import net.minecraft.server.world.ServerWorld; +import net.minecraft.text.LiteralText; +import net.minecraft.text.Text; +import net.minecraft.text.TranslatableText; +import net.minecraft.util.Identifier; +import net.minecraft.util.math.Vec2f; +import net.minecraft.util.math.Vec3d; +import net.minecraft.world.GameRules; + +import java.util.HashMap; +import java.util.Map; + +public class TileCommandComputer extends TileComputer +{ + public static final NamedBlockEntityType FACTORY = NamedBlockEntityType.create( + new Identifier( ComputerCraft.MOD_ID, "command_computer" ), + f -> new TileCommandComputer( ComputerFamily.Command, f ) + ); + + public class CommandReceiver implements CommandOutput + { + private final Map output = new HashMap<>(); + + public void clearOutput() + { + output.clear(); + } + + public Map getOutput() + { + return output; + } + + public Map copyOutput() + { + return new HashMap<>( output ); + } + + @Override + public void sendSystemMessage( Text textComponent ) + { + output.put( output.size() + 1, textComponent.getString() ); + } + + @Override + public boolean shouldReceiveFeedback() + { + return getWorld().getGameRules().getBoolean( GameRules.SEND_COMMAND_FEEDBACK ); + } + + @Override + public boolean shouldTrackOutput() + { + return true; + } + + @Override + public boolean shouldBroadcastConsoleToOps() + { + return getWorld().getGameRules().getBoolean( GameRules.COMMAND_BLOCK_OUTPUT ); + } + } + + private final CommandReceiver receiver; + + public TileCommandComputer( ComputerFamily family, BlockEntityType type ) + { + super( family, type ); + receiver = new CommandReceiver(); + } + + public CommandReceiver getReceiver() + { + return receiver; + } + + public ServerCommandSource getSource() + { + ServerComputer computer = getServerComputer(); + String name = "@"; + if( computer != null ) + { + String label = computer.getLabel(); + if( label != null ) name = label; + } + + return new ServerCommandSource( receiver, + new Vec3d( pos.getX() + 0.5, pos.getY() + 0.5, pos.getZ() + 0.5 ), Vec2f.ZERO, + (ServerWorld) getWorld(), 2, + name, new LiteralText( name ), + getWorld().getServer(), null + ); + } + + @Override + protected ServerComputer createComputer( int instanceID, int id ) + { + ServerComputer computer = super.createComputer( instanceID, id ); + computer.addAPI( new CommandAPI( this ) ); + return computer; + } + + @Override + public boolean isUsable( PlayerEntity player, boolean ignoreRange ) + { + MinecraftServer server = player.getServer(); + if( server == null || !server.areCommandBlocksEnabled() ) + { + player.sendMessage( new TranslatableText( "advMode.notEnabled" ), true ); + return false; + } + else if( !player.isCreativeLevelTwoOp() ) + { + player.sendMessage( new TranslatableText( "advMode.notAllowed" ), true ); + return false; + } + else + { + return super.isUsable( player, ignoreRange ); + } + } +} diff --git a/remappedSrc/dan200/computercraft/shared/computer/blocks/TileComputer.java b/remappedSrc/dan200/computercraft/shared/computer/blocks/TileComputer.java new file mode 100644 index 000000000..4aba3626e --- /dev/null +++ b/remappedSrc/dan200/computercraft/shared/computer/blocks/TileComputer.java @@ -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.shared.computer.blocks; + +import dan200.computercraft.ComputerCraft; +import dan200.computercraft.core.computer.ComputerSide; +import dan200.computercraft.shared.computer.core.ComputerFamily; +import dan200.computercraft.shared.computer.core.ComputerState; +import dan200.computercraft.shared.computer.core.ServerComputer; +import dan200.computercraft.shared.network.Containers; +import dan200.computercraft.shared.util.NamedBlockEntityType; +import net.minecraft.block.BlockState; +import net.minecraft.block.entity.BlockEntityType; +import net.minecraft.entity.player.PlayerEntity; +import net.minecraft.util.Identifier; +import net.minecraft.util.math.Direction; + +public class TileComputer extends TileComputerBase +{ + public static final NamedBlockEntityType FACTORY_NORMAL = NamedBlockEntityType.create( + new Identifier( ComputerCraft.MOD_ID, "computer_normal" ), + f -> new TileComputer( ComputerFamily.Normal, f ) + ); + + public static final NamedBlockEntityType FACTORY_ADVANCED = NamedBlockEntityType.create( + new Identifier( ComputerCraft.MOD_ID, "computer_advanced" ), + f -> new TileComputer( ComputerFamily.Advanced, f ) + ); + + private ComputerProxy m_proxy; + + public TileComputer( ComputerFamily family, BlockEntityType type ) + { + super( type, family ); + } + + @Override + protected ServerComputer createComputer( int instanceID, int id ) + { + ComputerFamily family = getFamily(); + ServerComputer computer = new ServerComputer( + getWorld(), id, m_label, instanceID, family, + ComputerCraft.terminalWidth_computer, + ComputerCraft.terminalHeight_computer + ); + computer.setPosition( getPos() ); + return computer; + } + + @Override + public ComputerProxy createProxy() + { + if( m_proxy == null ) + { + m_proxy = new ComputerProxy() + { + @Override + protected TileComputerBase getTile() + { + return TileComputer.this; + } + }; + } + return m_proxy; + } + + @Override + public void openGUI( PlayerEntity player ) + { + Containers.openComputerGUI( player, this ); + } + + public boolean isUsableByPlayer( PlayerEntity player ) + { + return isUsable( player, false ); + } + + @Override + public Direction getDirection() + { + return getCachedState().get( BlockComputer.FACING ); + } + + @Override + protected void updateBlockState( ComputerState newState ) + { + BlockState existing = getCachedState(); + if( existing.get( BlockComputer.STATE ) != newState ) + { + getWorld().setBlockState( getPos(), existing.with( BlockComputer.STATE, newState ), 3 ); + } + } + + @Override + protected ComputerSide remapLocalSide( ComputerSide localSide ) + { + // For legacy reasons, computers invert the meaning of "left" and "right". A computer's front is facing + // towards you, but a turtle's front is facing the other way. + if( localSide == ComputerSide.RIGHT ) return ComputerSide.LEFT; + if( localSide == ComputerSide.LEFT ) return ComputerSide.RIGHT; + return localSide; + } +} diff --git a/remappedSrc/dan200/computercraft/shared/computer/blocks/TileComputerBase.java b/remappedSrc/dan200/computercraft/shared/computer/blocks/TileComputerBase.java new file mode 100644 index 000000000..92420a69e --- /dev/null +++ b/remappedSrc/dan200/computercraft/shared/computer/blocks/TileComputerBase.java @@ -0,0 +1,459 @@ +/* + * 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.shared.computer.blocks; + +import dan200.computercraft.ComputerCraft; +import dan200.computercraft.api.peripheral.IPeripheral; +import dan200.computercraft.api.peripheral.IPeripheralTile; +import dan200.computercraft.core.computer.ComputerSide; +import dan200.computercraft.shared.BundledRedstone; +import dan200.computercraft.shared.Peripherals; +import dan200.computercraft.shared.common.TileGeneric; +import dan200.computercraft.shared.computer.core.ClientComputer; +import dan200.computercraft.shared.computer.core.ComputerFamily; +import dan200.computercraft.shared.computer.core.ComputerState; +import dan200.computercraft.shared.computer.core.ServerComputer; +import dan200.computercraft.shared.util.DirectionUtil; +import dan200.computercraft.shared.util.RedstoneUtil; +import joptsimple.internal.Strings; +import net.minecraft.block.BlockState; +import net.minecraft.block.Blocks; +import net.minecraft.block.RedstoneWireBlock; +import net.minecraft.block.entity.BlockEntityType; +import net.minecraft.entity.player.PlayerEntity; +import net.minecraft.item.ItemStack; +import net.minecraft.item.Items; +import net.minecraft.nbt.CompoundTag; +import net.minecraft.text.LiteralText; +import net.minecraft.text.Text; +import net.minecraft.util.Hand; +import net.minecraft.util.Nameable; +import net.minecraft.util.Tickable; +import net.minecraft.util.hit.BlockHitResult; +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; +import java.util.Objects; + +public abstract class TileComputerBase extends TileGeneric implements IComputerTile, Tickable, IPeripheralTile, Nameable +{ + private static final String NBT_ID = "ComputerId"; + private static final String NBT_LABEL = "Label"; + private static final String NBT_INSTANCE = "InstanceId"; + private static final String NBT_ON = "On"; + + private int m_instanceID = -1; + private int m_computerID = -1; + protected String m_label = null; + private boolean m_on = false; + boolean m_startOn = false; + private boolean m_fresh = false; + + private final ComputerFamily family; + + public TileComputerBase( BlockEntityType type, ComputerFamily family ) + { + super( type ); + this.family = family; + } + + protected void unload() + { + if( m_instanceID >= 0 ) + { + if( !getWorld().isClient ) ComputerCraft.serverComputerRegistry.remove( m_instanceID ); + m_instanceID = -1; + } + } + + @Override + public void destroy() + { + unload(); + for( Direction dir : DirectionUtil.FACINGS ) + { + RedstoneUtil.propagateRedstoneOutput( getWorld(), getPos(), dir ); + } + } + + /* + @Override + public void onChunkUnloaded() + { + unload(); + } + */ + + @Override + public void markRemoved() + { + unload(); + super.markRemoved(); + } + + public abstract void openGUI( PlayerEntity player ); + + protected boolean canNameWithTag( PlayerEntity player ) + { + return false; + } + + @Override + public boolean onActivate( PlayerEntity player, Hand hand, BlockHitResult hit ) + { + ItemStack currentItem = player.getStackInHand( hand ); + if( !currentItem.isEmpty() && currentItem.getItem() == Items.NAME_TAG && canNameWithTag( player ) && currentItem.hasCustomName() ) + { + // Label to rename computer + if( !getWorld().isClient ) + { + setLabel( currentItem.getName().asString() ); + currentItem.decrement( 1 ); + } + return true; + } + else if( !player.isSneaking() ) + { + // Regular right click to activate computer + if( !getWorld().isClient && isUsable( player, false ) ) + { + createServerComputer().turnOn(); + openGUI( player ); + } + return true; + } + return false; + } + + @Override + public void onNeighbourChange( @Nonnull BlockPos neighbour ) + { + updateInput( neighbour ); + } + + @Override + public void onNeighbourTileEntityChange( @Nonnull BlockPos neighbour ) + { + updateInput( neighbour ); + } + + @Override + public void tick() + { + if( !getWorld().isClient ) + { + ServerComputer computer = createServerComputer(); + if( computer == null ) return; + + // If the computer isn't on and should be, then turn it on + if( m_startOn || (m_fresh && m_on) ) + { + computer.turnOn(); + m_startOn = false; + } + + computer.keepAlive(); + + m_fresh = false; + m_computerID = computer.getID(); + m_label = computer.getLabel(); + m_on = computer.isOn(); + + if( computer.hasOutputChanged() ) updateOutput(); + + // Update the block state if needed. We don't fire a block update intentionally, + // as this only really is needed on the client side. + updateBlockState( computer.getState() ); + + if( computer.hasOutputChanged() ) updateOutput(); + } + else + { + ClientComputer computer = createClientComputer(); + if( computer != null && computer.hasOutputChanged() ) updateBlock(); + } + } + + protected abstract void updateBlockState( ComputerState newState ); + + @Nonnull + @Override + public CompoundTag toTag( CompoundTag nbt ) + { + // Save ID, label and power state + if( m_computerID >= 0 ) nbt.putInt( NBT_ID, m_computerID ); + if( m_label != null ) nbt.putString( NBT_LABEL, m_label ); + nbt.putBoolean( NBT_ON, m_on ); + + return super.toTag( nbt ); + } + + @Override + public void fromTag( CompoundTag nbt ) + { + super.fromTag( nbt ); + + // Load ID, label and power state + m_computerID = nbt.contains( NBT_ID ) ? nbt.getInt( NBT_ID ) : -1; + m_label = nbt.contains( NBT_LABEL ) ? nbt.getString( NBT_LABEL ) : null; + m_on = m_startOn = nbt.getBoolean( NBT_ON ); + } + + protected boolean isPeripheralBlockedOnSide( ComputerSide localSide ) + { + return false; + } + + protected abstract Direction getDirection(); + + protected ComputerSide remapToLocalSide( Direction globalSide ) + { + return remapLocalSide( DirectionUtil.toLocal( getDirection(), globalSide ) ); + } + + protected ComputerSide remapLocalSide( ComputerSide localSide ) + { + return localSide; + } + + private void updateSideInput( ServerComputer computer, Direction dir, BlockPos offset ) + { + Direction offsetSide = dir.getOpposite(); + ComputerSide localDir = remapToLocalSide( dir ); + + computer.setRedstoneInput( localDir, getRedstoneInput( world, offset, dir ) ); + computer.setBundledRedstoneInput( localDir, BundledRedstone.getOutput( getWorld(), offset, offsetSide ) ); + if( !isPeripheralBlockedOnSide( localDir ) ) + { + computer.setPeripheral( localDir, Peripherals.getPeripheral( getWorld(), offset, offsetSide ) ); + } + } + + /** + * Gets the redstone input for an adjacent block + * + * @param world The world we exist in + * @param pos The position of the neighbour + * @param side The side we are reading from + * @return The effective redstone power + * @see net.minecraft.block.RedstoneBlock#method_9991(World, BlockPos, BlockState) + */ + protected static int getRedstoneInput( World world, BlockPos pos, Direction side ) + { + int power = world.getEmittedRedstonePower( pos, side ); + if( power >= 15 ) return power; + + BlockState neighbour = world.getBlockState( pos ); + return neighbour.getBlock() == Blocks.REDSTONE_WIRE + ? Math.max( power, neighbour.get( RedstoneWireBlock.POWER ) ) + : power; + } + + public void updateInput() + { + if( getWorld() == null || getWorld().isClient ) return; + + // Update all sides + ServerComputer computer = getServerComputer(); + if( computer == null ) return; + + BlockPos pos = computer.getPosition(); + for( Direction dir : DirectionUtil.FACINGS ) + { + updateSideInput( computer, dir, pos.offset( dir ) ); + } + } + + private void updateInput( BlockPos neighbour ) + { + if( getWorld() == null || getWorld().isClient ) return; + + ServerComputer computer = getServerComputer(); + if( computer == null ) return; + + BlockPos pos = computer.getPosition(); + for( Direction dir : DirectionUtil.FACINGS ) + { + BlockPos offset = pos.offset( dir ); + if( offset.equals( neighbour ) ) + { + updateSideInput( computer, dir, offset ); + break; + } + } + + // If the position is not any adjacent one, update all inputs. + updateInput(); + } + + public void updateOutput() + { + // Update redstone + updateBlock(); + for( Direction dir : DirectionUtil.FACINGS ) + { + RedstoneUtil.propagateRedstoneOutput( getWorld(), getPos(), dir ); + } + } + + protected abstract ServerComputer createComputer( int instanceID, int id ); + + public abstract ComputerProxy createProxy(); + + @Override + public final int getComputerID() + { + return m_computerID; + } + + @Override + public final String getLabel() + { + return m_label; + } + + @Override + public final void setComputerID( int id ) + { + if( getWorld().isClient || m_computerID == id ) return; + + m_computerID = id; + ServerComputer computer = getServerComputer(); + if( computer != null ) computer.setID( m_computerID ); + markDirty(); + } + + @Override + public final void setLabel( String label ) + { + if( getWorld().isClient || Objects.equals( m_label, label ) ) return; + + m_label = label; + ServerComputer computer = getServerComputer(); + if( computer != null ) computer.setLabel( label ); + markDirty(); + } + + @Override + public ComputerFamily getFamily() + { + return family; + } + + public ServerComputer createServerComputer() + { + if( getWorld().isClient ) return null; + + boolean changed = false; + if( m_instanceID < 0 ) + { + m_instanceID = ComputerCraft.serverComputerRegistry.getUnusedInstanceID(); + changed = true; + } + if( !ComputerCraft.serverComputerRegistry.contains( m_instanceID ) ) + { + ServerComputer computer = createComputer( m_instanceID, m_computerID ); + ComputerCraft.serverComputerRegistry.add( m_instanceID, computer ); + m_fresh = true; + changed = true; + } + if( changed ) + { + updateBlock(); + updateInput(); + } + return ComputerCraft.serverComputerRegistry.get( m_instanceID ); + } + + public ServerComputer getServerComputer() + { + return getWorld().isClient ? null : ComputerCraft.serverComputerRegistry.get( m_instanceID ); + } + + public ClientComputer createClientComputer() + { + if( !getWorld().isClient || m_instanceID < 0 ) return null; + + ClientComputer computer = ComputerCraft.clientComputerRegistry.get( m_instanceID ); + if( computer == null ) + { + ComputerCraft.clientComputerRegistry.add( m_instanceID, computer = new ClientComputer( m_instanceID ) ); + } + return computer; + } + + public ClientComputer getClientComputer() + { + return getWorld().isClient ? ComputerCraft.clientComputerRegistry.get( m_instanceID ) : null; + } + + // Networking stuff + + @Override + protected void writeDescription( @Nonnull CompoundTag nbt ) + { + super.writeDescription( nbt ); + + if( m_computerID >= 0 ) nbt.putInt( NBT_ID, m_computerID ); + if( m_label != null ) nbt.putString( NBT_LABEL, m_label ); + nbt.putInt( NBT_INSTANCE, createServerComputer().getInstanceID() ); + } + + @Override + protected void readDescription( @Nonnull CompoundTag nbt ) + { + super.readDescription( nbt ); + m_instanceID = nbt.contains( NBT_INSTANCE ) ? nbt.getInt( NBT_INSTANCE ) : -1; + m_label = nbt.contains( NBT_LABEL ) ? nbt.getString( NBT_LABEL ) : null; + m_computerID = nbt.contains( NBT_ID ) ? nbt.getInt( NBT_ID ) : -1; + } + + protected void transferStateFrom( TileComputerBase copy ) + { + if( copy.m_computerID != m_computerID || copy.m_instanceID != m_instanceID ) + { + unload(); + m_instanceID = copy.m_instanceID; + m_computerID = copy.m_computerID; + m_label = copy.m_label; + m_on = copy.m_on; + m_startOn = copy.m_startOn; + updateBlock(); + } + copy.m_instanceID = -1; + } + + @Nullable + @Override + public IPeripheral getPeripheral( @Nonnull Direction side ) + { + return new ComputerPeripheral( "computer", createProxy() ); + } + + @Nonnull + @Override + public Text getName() + { + return hasCustomName() ? new LiteralText( m_label ) : getCachedState().getBlock().getName(); + } + + @Override + public boolean hasCustomName() + { + return !Strings.isNullOrEmpty( m_label ); + } + + @Nullable + @Override + public Text getCustomName() + { + return hasCustomName() ? new LiteralText( m_label ) : null; + } +} diff --git a/remappedSrc/dan200/computercraft/shared/computer/core/ClientComputer.java b/remappedSrc/dan200/computercraft/shared/computer/core/ClientComputer.java new file mode 100644 index 000000000..e71d82c8d --- /dev/null +++ b/remappedSrc/dan200/computercraft/shared/computer/core/ClientComputer.java @@ -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.shared.computer.core; + +import com.google.common.base.Objects; +import dan200.computercraft.shared.common.ClientTerminal; +import dan200.computercraft.shared.network.NetworkHandler; +import dan200.computercraft.shared.network.server.*; +import net.minecraft.nbt.CompoundTag; + +public class ClientComputer extends ClientTerminal implements IComputer +{ + private final int m_instanceID; + + private boolean m_on = false; + private boolean m_blinking = false; + private boolean m_changed = true; + private CompoundTag m_userData = null; + + private boolean m_changedLastFrame = false; + + public ClientComputer( int instanceID ) + { + super( false ); + m_instanceID = instanceID; + } + + public void update() + { + m_changedLastFrame = m_changed; + m_changed = false; + } + + public boolean hasOutputChanged() + { + return m_changedLastFrame; + } + + public CompoundTag getUserData() + { + return m_userData; + } + + public void requestState() + { + // Request state from server + NetworkHandler.sendToServer( new RequestComputerMessage( getInstanceID() ) ); + } + + // IComputer + + @Override + public int getInstanceID() + { + return m_instanceID; + } + + @Override + public boolean isOn() + { + return m_on; + } + + @Override + public boolean isCursorDisplayed() + { + return m_on && m_blinking; + } + + @Override + public void turnOn() + { + // Send turnOn to server + NetworkHandler.sendToServer( new ComputerActionServerMessage( m_instanceID, ComputerActionServerMessage.Action.TURN_ON ) ); + } + + @Override + public void shutdown() + { + // Send shutdown to server + NetworkHandler.sendToServer( new ComputerActionServerMessage( m_instanceID, ComputerActionServerMessage.Action.SHUTDOWN ) ); + } + + @Override + public void reboot() + { + // Send reboot to server + NetworkHandler.sendToServer( new ComputerActionServerMessage( m_instanceID, ComputerActionServerMessage.Action.REBOOT ) ); + } + + @Override + public void queueEvent( String event, Object[] arguments ) + { + // Send event to server + NetworkHandler.sendToServer( new QueueEventServerMessage( m_instanceID, event, arguments ) ); + } + + @Override + public void keyDown( int key, boolean repeat ) + { + NetworkHandler.sendToServer( new KeyEventServerMessage( m_instanceID, repeat ? KeyEventServerMessage.TYPE_REPEAT : KeyEventServerMessage.TYPE_DOWN, key ) ); + } + + @Override + public void keyUp( int key ) + { + NetworkHandler.sendToServer( new KeyEventServerMessage( m_instanceID, KeyEventServerMessage.TYPE_UP, key ) ); + } + + @Override + public void mouseClick( int button, int x, int y ) + { + NetworkHandler.sendToServer( new MouseEventServerMessage( m_instanceID, MouseEventServerMessage.TYPE_CLICK, button, x, y ) ); + } + + @Override + public void mouseUp( int button, int x, int y ) + { + NetworkHandler.sendToServer( new MouseEventServerMessage( m_instanceID, MouseEventServerMessage.TYPE_UP, button, x, y ) ); + } + + @Override + public void mouseDrag( int button, int x, int y ) + { + NetworkHandler.sendToServer( new MouseEventServerMessage( m_instanceID, MouseEventServerMessage.TYPE_DRAG, button, x, y ) ); + } + + @Override + public void mouseScroll( int direction, int x, int y ) + { + NetworkHandler.sendToServer( new MouseEventServerMessage( m_instanceID, MouseEventServerMessage.TYPE_SCROLL, direction, x, y ) ); + } + + public void setState( ComputerState state, CompoundTag userData ) + { + boolean oldOn = m_on; + boolean oldBlinking = m_blinking; + CompoundTag oldUserData = m_userData; + + m_on = state != ComputerState.OFF; + m_blinking = state == ComputerState.BLINKING; + m_userData = userData; + + m_changed |= m_on != oldOn || m_blinking != oldBlinking || !Objects.equal( m_userData, oldUserData ); + } +} diff --git a/remappedSrc/dan200/computercraft/shared/computer/core/ClientComputerRegistry.java b/remappedSrc/dan200/computercraft/shared/computer/core/ClientComputerRegistry.java new file mode 100644 index 000000000..8d6b510ba --- /dev/null +++ b/remappedSrc/dan200/computercraft/shared/computer/core/ClientComputerRegistry.java @@ -0,0 +1,25 @@ +/* + * 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.shared.computer.core; + +public class ClientComputerRegistry extends ComputerRegistry +{ + public void update() + { + for( ClientComputer computer : getComputers() ) + { + computer.update(); + } + } + + @Override + public void add( int instanceID, ClientComputer computer ) + { + super.add( instanceID, computer ); + computer.requestState(); + } +} diff --git a/remappedSrc/dan200/computercraft/shared/computer/core/ComputerFamily.java b/remappedSrc/dan200/computercraft/shared/computer/core/ComputerFamily.java new file mode 100644 index 000000000..30fa94f3c --- /dev/null +++ b/remappedSrc/dan200/computercraft/shared/computer/core/ComputerFamily.java @@ -0,0 +1,14 @@ +/* + * 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.shared.computer.core; + +public enum ComputerFamily +{ + Normal, + Advanced, + Command +} diff --git a/remappedSrc/dan200/computercraft/shared/computer/core/ComputerRegistry.java b/remappedSrc/dan200/computercraft/shared/computer/core/ComputerRegistry.java new file mode 100644 index 000000000..ca9e8c567 --- /dev/null +++ b/remappedSrc/dan200/computercraft/shared/computer/core/ComputerRegistry.java @@ -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.shared.computer.core; + +import java.util.Collection; +import java.util.HashMap; +import java.util.Map; +import java.util.Random; + +public class ComputerRegistry +{ + private Map m_computers; + private int m_nextUnusedInstanceID; + private int m_sessionID; + + protected ComputerRegistry() + { + m_computers = new HashMap<>(); + reset(); + } + + public int getSessionID() + { + return m_sessionID; + } + + public int getUnusedInstanceID() + { + return m_nextUnusedInstanceID++; + } + + public Collection getComputers() + { + return m_computers.values(); + } + + public TComputer get( int instanceID ) + { + if( instanceID >= 0 ) + { + if( m_computers.containsKey( instanceID ) ) + { + return m_computers.get( instanceID ); + } + } + return null; + } + + public boolean contains( int instanceID ) + { + return m_computers.containsKey( instanceID ); + } + + public void add( int instanceID, TComputer computer ) + { + if( m_computers.containsKey( instanceID ) ) + { + remove( instanceID ); + } + m_computers.put( instanceID, computer ); + m_nextUnusedInstanceID = Math.max( m_nextUnusedInstanceID, instanceID + 1 ); + } + + public void remove( int instanceID ) + { + m_computers.remove( instanceID ); + } + + public void reset() + { + m_computers.clear(); + m_nextUnusedInstanceID = 0; + m_sessionID = new Random().nextInt(); + } +} diff --git a/remappedSrc/dan200/computercraft/shared/computer/core/ComputerState.java b/remappedSrc/dan200/computercraft/shared/computer/core/ComputerState.java new file mode 100644 index 000000000..73c67a15b --- /dev/null +++ b/remappedSrc/dan200/computercraft/shared/computer/core/ComputerState.java @@ -0,0 +1,39 @@ +/* + * 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.shared.computer.core; + +import net.minecraft.util.StringIdentifiable; + +import javax.annotation.Nonnull; + +public enum ComputerState implements StringIdentifiable +{ + OFF( "off" ), + ON( "on" ), + BLINKING( "blinking" ); + + private final String name; + + ComputerState( String name ) + { + this.name = name; + } + + @Nonnull + @Override + public String asString() + { + return name; + } + + @Override + public String toString() + { + return name; + } +} + diff --git a/remappedSrc/dan200/computercraft/shared/computer/core/IComputer.java b/remappedSrc/dan200/computercraft/shared/computer/core/IComputer.java new file mode 100644 index 000000000..249d5b7c9 --- /dev/null +++ b/remappedSrc/dan200/computercraft/shared/computer/core/IComputer.java @@ -0,0 +1,38 @@ +/* + * 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.shared.computer.core; + +import dan200.computercraft.shared.common.ITerminal; + +public interface IComputer extends ITerminal, InputHandler +{ + int getInstanceID(); + + boolean isOn(); + + boolean isCursorDisplayed(); + + void turnOn(); + + void shutdown(); + + void reboot(); + + @Override + void queueEvent( String event, Object[] arguments ); + + default void queueEvent( String event ) + { + queueEvent( event, null ); + } + + default ComputerState getState() + { + if( !isOn() ) return ComputerState.OFF; + return isCursorDisplayed() ? ComputerState.BLINKING : ComputerState.ON; + } +} diff --git a/remappedSrc/dan200/computercraft/shared/computer/core/IContainerComputer.java b/remappedSrc/dan200/computercraft/shared/computer/core/IContainerComputer.java new file mode 100644 index 000000000..85acfbae0 --- /dev/null +++ b/remappedSrc/dan200/computercraft/shared/computer/core/IContainerComputer.java @@ -0,0 +1,39 @@ +/* + * 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.shared.computer.core; + +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +/** + * An instance of {@link net.minecraft.inventory.Container} which provides a computer. You should implement this + * if you provide custom computers/GUIs to interact with them. + */ +@FunctionalInterface +public interface IContainerComputer +{ + /** + * Get the computer you are interacting with. + * + * This will only be called on the server. + * + * @return The computer you are interacting with. + */ + @Nullable + IComputer getComputer(); + + /** + * Get the input controller for this container. + * + * @return This container's input. + */ + @Nonnull + default InputState getInput() + { + return new InputState( this ); + } +} diff --git a/remappedSrc/dan200/computercraft/shared/computer/core/InputHandler.java b/remappedSrc/dan200/computercraft/shared/computer/core/InputHandler.java new file mode 100644 index 000000000..fc451fea9 --- /dev/null +++ b/remappedSrc/dan200/computercraft/shared/computer/core/InputHandler.java @@ -0,0 +1,48 @@ +/* + * 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.shared.computer.core; + +/** + * Receives some input and forwards it to a computer + * + * @see InputState + * @see IComputer + */ +public interface InputHandler +{ + void queueEvent( String event, Object[] arguments ); + + default void keyDown( int key, boolean repeat ) + { + queueEvent( "key", new Object[] { key, repeat } ); + } + + default void keyUp( int key ) + { + queueEvent( "key_up", new Object[] { key } ); + } + + default void mouseClick( int button, int x, int y ) + { + queueEvent( "mouse_click", new Object[] { button, x, y } ); + } + + default void mouseUp( int button, int x, int y ) + { + queueEvent( "mouse_up", new Object[] { button, x, y } ); + } + + default void mouseDrag( int button, int x, int y ) + { + queueEvent( "mouse_drag", new Object[] { button, x, y } ); + } + + default void mouseScroll( int direction, int x, int y ) + { + queueEvent( "mouse_scroll", new Object[] { direction, x, y } ); + } +} diff --git a/remappedSrc/dan200/computercraft/shared/computer/core/InputState.java b/remappedSrc/dan200/computercraft/shared/computer/core/InputState.java new file mode 100644 index 000000000..4ee3420ec --- /dev/null +++ b/remappedSrc/dan200/computercraft/shared/computer/core/InputState.java @@ -0,0 +1,111 @@ +/* + * 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.shared.computer.core; + +import it.unimi.dsi.fastutil.ints.IntIterator; +import it.unimi.dsi.fastutil.ints.IntOpenHashSet; +import it.unimi.dsi.fastutil.ints.IntSet; + +/** + * An {@link InputHandler} which keeps track of the current key and mouse state, and releases them when the container + * is closed. + */ +public class InputState implements InputHandler +{ + private final IContainerComputer owner; + private final IntSet keysDown = new IntOpenHashSet( 4 ); + + private int lastMouseX; + private int lastMouseY; + private int lastMouseDown = -1; + + public InputState( IContainerComputer owner ) + { + this.owner = owner; + } + + @Override + public void queueEvent( String event, Object[] arguments ) + { + IComputer computer = owner.getComputer(); + if( computer != null ) computer.queueEvent( event, arguments ); + } + + @Override + public void keyDown( int key, boolean repeat ) + { + keysDown.add( key ); + IComputer computer = owner.getComputer(); + if( computer != null ) computer.keyDown( key, repeat ); + } + + @Override + public void keyUp( int key ) + { + keysDown.remove( key ); + IComputer computer = owner.getComputer(); + if( computer != null ) computer.keyUp( key ); + } + + @Override + public void mouseClick( int button, int x, int y ) + { + lastMouseX = x; + lastMouseY = y; + lastMouseDown = button; + + IComputer computer = owner.getComputer(); + if( computer != null ) computer.mouseClick( button, x, y ); + } + + @Override + public void mouseUp( int button, int x, int y ) + { + lastMouseX = x; + lastMouseY = y; + lastMouseDown = -1; + + IComputer computer = owner.getComputer(); + if( computer != null ) computer.mouseUp( button, x, y ); + } + + @Override + public void mouseDrag( int button, int x, int y ) + { + lastMouseX = x; + lastMouseY = y; + lastMouseDown = button; + + IComputer computer = owner.getComputer(); + if( computer != null ) computer.mouseDrag( button, x, y ); + } + + @Override + public void mouseScroll( int direction, int x, int y ) + { + lastMouseX = x; + lastMouseY = y; + + IComputer computer = owner.getComputer(); + if( computer != null ) computer.mouseScroll( direction, x, y ); + } + + public void close() + { + IComputer computer = owner.getComputer(); + if( computer != null ) + { + IntIterator keys = keysDown.iterator(); + while( keys.hasNext() ) computer.keyUp( keys.nextInt() ); + + if( lastMouseDown != -1 ) computer.mouseUp( lastMouseDown, lastMouseX, lastMouseY ); + } + + keysDown.clear(); + lastMouseDown = -1; + } +} diff --git a/remappedSrc/dan200/computercraft/shared/computer/core/ServerComputer.java b/remappedSrc/dan200/computercraft/shared/computer/core/ServerComputer.java new file mode 100644 index 000000000..1ccaed849 --- /dev/null +++ b/remappedSrc/dan200/computercraft/shared/computer/core/ServerComputer.java @@ -0,0 +1,378 @@ +/* + * 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.shared.computer.core; + +import dan200.computercraft.ComputerCraft; +import dan200.computercraft.api.ComputerCraftAPI; +import dan200.computercraft.api.filesystem.IMount; +import dan200.computercraft.api.filesystem.IWritableMount; +import dan200.computercraft.api.lua.ILuaAPI; +import dan200.computercraft.api.peripheral.IPeripheral; +import dan200.computercraft.core.apis.IAPIEnvironment; +import dan200.computercraft.core.computer.Computer; +import dan200.computercraft.core.computer.ComputerSide; +import dan200.computercraft.core.computer.IComputerEnvironment; +import dan200.computercraft.shared.common.ServerTerminal; +import dan200.computercraft.shared.network.NetworkHandler; +import dan200.computercraft.shared.network.NetworkMessage; +import dan200.computercraft.shared.network.client.ComputerDataClientMessage; +import dan200.computercraft.shared.network.client.ComputerDeletedClientMessage; +import dan200.computercraft.shared.network.client.ComputerTerminalClientMessage; +import net.minecraft.SharedConstants; +import net.minecraft.entity.player.PlayerEntity; +import net.minecraft.nbt.CompoundTag; +import net.minecraft.screen.ScreenHandler; +import net.minecraft.server.MinecraftServer; +import net.minecraft.util.math.BlockPos; +import net.minecraft.world.World; + +import javax.annotation.Nullable; +import java.io.InputStream; + +public class ServerComputer extends ServerTerminal implements IComputer, IComputerEnvironment +{ + private final int m_instanceID; + + private World m_world; + private BlockPos m_position; + + private final ComputerFamily m_family; + private final Computer m_computer; + private CompoundTag m_userData; + private boolean m_changed; + + private boolean m_changedLastFrame; + private int m_ticksSincePing; + + public ServerComputer( World world, int computerID, String label, int instanceID, ComputerFamily family, int terminalWidth, int terminalHeight ) + { + super( family != ComputerFamily.Normal, terminalWidth, terminalHeight ); + m_instanceID = instanceID; + + m_world = world; + m_position = null; + + m_family = family; + m_computer = new Computer( this, getTerminal(), computerID ); + m_computer.setLabel( label ); + m_userData = null; + m_changed = false; + + m_changedLastFrame = false; + m_ticksSincePing = 0; + } + + public ComputerFamily getFamily() + { + return m_family; + } + + public World getWorld() + { + return m_world; + } + + public void setWorld( World world ) + { + m_world = world; + } + + public BlockPos getPosition() + { + return m_position; + } + + public void setPosition( BlockPos pos ) + { + m_position = new BlockPos( pos ); + } + + public IAPIEnvironment getAPIEnvironment() + { + return m_computer.getAPIEnvironment(); + } + + public Computer getComputer() + { + return m_computer; + } + + @Override + public void update() + { + super.update(); + m_computer.tick(); + + m_changedLastFrame = m_computer.pollAndResetChanged() || m_changed; + m_changed = false; + + m_ticksSincePing++; + } + + public void keepAlive() + { + m_ticksSincePing = 0; + } + + public boolean hasTimedOut() + { + return m_ticksSincePing > 100; + } + + public boolean hasOutputChanged() + { + return m_changedLastFrame; + } + + public void unload() + { + m_computer.unload(); + } + + public CompoundTag getUserData() + { + if( m_userData == null ) + { + m_userData = new CompoundTag(); + } + return m_userData; + } + + public void updateUserData() + { + m_changed = true; + } + + private NetworkMessage createComputerPacket() + { + return new ComputerDataClientMessage( this ); + } + + protected NetworkMessage createTerminalPacket() + { + CompoundTag tagCompound = new CompoundTag(); + writeDescription( tagCompound ); + return new ComputerTerminalClientMessage( getInstanceID(), tagCompound ); + } + + public void broadcastState( boolean force ) + { + MinecraftServer server = m_world == null ? null : m_world.getServer(); + if( server == null ) return; + + if( hasOutputChanged() || force ) + { + // Send computer state to all clients + NetworkHandler.sendToAllPlayers( server, createComputerPacket() ); + } + + if( hasTerminalChanged() || force ) + { + // Send terminal state to clients who are currently interacting with the computer. + + NetworkMessage packet = null; + for( PlayerEntity player : server.getPlayerManager().getPlayerList() ) + { + if( isInteracting( player ) ) + { + if( packet == null ) packet = createTerminalPacket(); + NetworkHandler.sendToPlayer( player, packet ); + } + } + } + } + + public void sendComputerState( PlayerEntity player ) + { + // Send state to client + NetworkHandler.sendToPlayer( player, createComputerPacket() ); + } + + public void sendTerminalState( PlayerEntity player ) + { + // Send terminal state to client + NetworkHandler.sendToPlayer( player, createTerminalPacket() ); + } + + public void broadcastDelete() + { + // Send deletion to client + MinecraftServer server = m_world == null ? null : m_world.getServer(); + if( server == null ) return; + NetworkHandler.sendToAllPlayers( server, new ComputerDeletedClientMessage( getInstanceID() ) ); + } + + public void setID( int id ) + { + m_computer.setID( id ); + } + + // IComputer + + @Override + public int getInstanceID() + { + return m_instanceID; + } + + public int getID() + { + return m_computer.getID(); + } + + public String getLabel() + { + return m_computer.getLabel(); + } + + @Override + public boolean isOn() + { + return m_computer.isOn(); + } + + @Override + public boolean isCursorDisplayed() + { + return m_computer.isOn() && m_computer.isBlinking(); + } + + @Override + public void turnOn() + { + // Turn on + m_computer.turnOn(); + } + + @Override + public void shutdown() + { + // Shutdown + m_computer.shutdown(); + } + + @Override + public void reboot() + { + // Reboot + m_computer.reboot(); + } + + @Override + public void queueEvent( String event, Object[] arguments ) + { + // Queue event + m_computer.queueEvent( event, arguments ); + } + + public int getRedstoneOutput( ComputerSide side ) + { + return m_computer.getEnvironment().getExternalRedstoneOutput( side ); + } + + public void setRedstoneInput( ComputerSide side, int level ) + { + m_computer.getEnvironment().setRedstoneInput( side, level ); + } + + public int getBundledRedstoneOutput( ComputerSide side ) + { + return m_computer.getEnvironment().getExternalBundledRedstoneOutput( side ); + } + + public void setBundledRedstoneInput( ComputerSide side, int combination ) + { + m_computer.getEnvironment().setBundledRedstoneInput( side, combination ); + } + + public void addAPI( ILuaAPI api ) + { + m_computer.addApi( api ); + } + + public void setPeripheral( ComputerSide side, IPeripheral peripheral ) + { + m_computer.getEnvironment().setPeripheral( side, peripheral ); + } + + public IPeripheral getPeripheral( ComputerSide side ) + { + return m_computer.getEnvironment().getPeripheral( side ); + } + + public void setLabel( String label ) + { + m_computer.setLabel( label ); + } + + // IComputerEnvironment implementation + + @Override + public double getTimeOfDay() + { + return (m_world.getTimeOfDay() + 6000) % 24000 / 1000.0; + } + + @Override + public int getDay() + { + return (int) ((m_world.getTimeOfDay() + 6000) / 24000) + 1; + } + + @Override + public IWritableMount createSaveDirMount( String subPath, long capacity ) + { + return ComputerCraftAPI.createSaveDirMount( m_world, subPath, capacity ); + } + + @Override + public IMount createResourceMount( String domain, String subPath ) + { + return ComputerCraftAPI.createResourceMount( domain, subPath ); + } + + @Override + public InputStream createResourceFile( String domain, String subPath ) + { + return ComputerCraft.getResourceFile( domain, subPath ); + } + + @Override + public long getComputerSpaceLimit() + { + return ComputerCraft.computerSpaceLimit; + } + + @Override + public String getHostString() + { + return "ComputerCraft ${version} (Minecraft " + SharedConstants.getGameVersion() + ")"; + } + + @Override + public int assignNewID() + { + return ComputerCraftAPI.createUniqueNumberedSaveDir( m_world, "computer" ); + } + + @Nullable + public IContainerComputer getContainer( PlayerEntity player ) + { + if( player == null ) return null; + + ScreenHandler container = player.currentScreenHandler; + if( !(container instanceof IContainerComputer) ) return null; + + IContainerComputer computerContainer = (IContainerComputer) container; + return computerContainer.getComputer() != this ? null : computerContainer; + } + + protected boolean isInteracting( PlayerEntity player ) + { + return getContainer( player ) != null; + } +} diff --git a/remappedSrc/dan200/computercraft/shared/computer/core/ServerComputerRegistry.java b/remappedSrc/dan200/computercraft/shared/computer/core/ServerComputerRegistry.java new file mode 100644 index 000000000..591597d9d --- /dev/null +++ b/remappedSrc/dan200/computercraft/shared/computer/core/ServerComputerRegistry.java @@ -0,0 +1,83 @@ +/* + * 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.shared.computer.core; + +import java.util.Iterator; + +public class ServerComputerRegistry extends ComputerRegistry +{ + public void update() + { + Iterator it = getComputers().iterator(); + while( it.hasNext() ) + { + ServerComputer computer = it.next(); + if( computer.hasTimedOut() ) + { + //System.out.println( "TIMED OUT SERVER COMPUTER " + computer.getInstanceID() ); + computer.unload(); + computer.broadcastDelete(); + it.remove(); + //System.out.println( getComputers().size() + " SERVER COMPUTERS" ); + } + else + { + computer.update(); + if( computer.hasTerminalChanged() || computer.hasOutputChanged() ) + { + computer.broadcastState( false ); + } + } + } + } + + @Override + public void add( int instanceID, ServerComputer computer ) + { + //System.out.println( "ADD SERVER COMPUTER " + instanceID ); + super.add( instanceID, computer ); + computer.broadcastState( true ); + //System.out.println( getComputers().size() + " SERVER COMPUTERS" ); + } + + @Override + public void remove( int instanceID ) + { + //System.out.println( "REMOVE SERVER COMPUTER " + instanceID ); + ServerComputer computer = get( instanceID ); + if( computer != null ) + { + computer.unload(); + computer.broadcastDelete(); + } + super.remove( instanceID ); + //System.out.println( getComputers().size() + " SERVER COMPUTERS" ); + } + + @Override + public void reset() + { + //System.out.println( "RESET SERVER COMPUTERS" ); + for( ServerComputer computer : getComputers() ) + { + computer.unload(); + } + super.reset(); + //System.out.println( getComputers().size() + " SERVER COMPUTERS" ); + } + + public ServerComputer lookup( int computerID ) + { + if( computerID < 0 ) return null; + + for( ServerComputer computer : getComputers() ) + { + if( computer.getID() == computerID ) return computer; + } + return null; + } +} diff --git a/remappedSrc/dan200/computercraft/shared/computer/inventory/ContainerComputer.java b/remappedSrc/dan200/computercraft/shared/computer/inventory/ContainerComputer.java new file mode 100644 index 000000000..59cd62dba --- /dev/null +++ b/remappedSrc/dan200/computercraft/shared/computer/inventory/ContainerComputer.java @@ -0,0 +1,55 @@ +/* + * 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.shared.computer.inventory; + +import dan200.computercraft.shared.computer.blocks.TileComputer; +import dan200.computercraft.shared.computer.core.IComputer; +import dan200.computercraft.shared.computer.core.IContainerComputer; +import dan200.computercraft.shared.computer.core.InputState; +import net.minecraft.entity.player.PlayerEntity; +import net.minecraft.screen.ScreenHandler; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class ContainerComputer extends ScreenHandler implements IContainerComputer +{ + private final TileComputer computer; + private final InputState input = new InputState( this ); + + public ContainerComputer( int id, TileComputer computer ) + { + super( null, id ); + this.computer = computer; + } + + @Override + public boolean canUse( @Nonnull PlayerEntity player ) + { + return computer.isUsableByPlayer( player ); + } + + @Nullable + @Override + public IComputer getComputer() + { + return computer.getServerComputer(); + } + + @Nonnull + @Override + public InputState getInput() + { + return input; + } + + @Override + public void close( PlayerEntity player ) + { + super.close( player ); + input.close(); + } +} diff --git a/remappedSrc/dan200/computercraft/shared/computer/inventory/ContainerViewComputer.java b/remappedSrc/dan200/computercraft/shared/computer/inventory/ContainerViewComputer.java new file mode 100644 index 000000000..93ecbe8b4 --- /dev/null +++ b/remappedSrc/dan200/computercraft/shared/computer/inventory/ContainerViewComputer.java @@ -0,0 +1,83 @@ +/* + * 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.shared.computer.inventory; + +import dan200.computercraft.ComputerCraft; +import dan200.computercraft.shared.computer.core.*; +import net.minecraft.entity.player.PlayerEntity; +import net.minecraft.screen.ScreenHandler; +import net.minecraft.server.MinecraftServer; +import net.minecraft.text.TranslatableText; + +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class ContainerViewComputer extends ScreenHandler implements IContainerComputer +{ + private final IComputer computer; + private final InputState input = new InputState( this ); + + public ContainerViewComputer( int id, IComputer computer ) + { + super( null, id ); + this.computer = computer; + } + + @Nullable + @Override + public IComputer getComputer() + { + return computer; + } + + @Override + public boolean canUse( @Nonnull PlayerEntity player ) + { + if( computer instanceof ServerComputer ) + { + ServerComputer serverComputer = (ServerComputer) computer; + + // If this computer no longer exists then discard it. + if( ComputerCraft.serverComputerRegistry.get( serverComputer.getInstanceID() ) != serverComputer ) + { + return false; + } + + // If we're a command computer then ensure we're in creative + if( serverComputer.getFamily() == ComputerFamily.Command ) + { + MinecraftServer server = player.getServer(); + if( server == null || !server.areCommandBlocksEnabled() ) + { + player.sendMessage( new TranslatableText( "advMode.notEnabled" ), false ); + return false; + } + else if( !player.isCreativeLevelTwoOp() ) + { + player.sendMessage( new TranslatableText( "advMode.notAllowed" ), false ); + return false; + } + } + } + + return true; + } + + @Nonnull + @Override + public InputState getInput() + { + return input; + } + + @Override + public void close( PlayerEntity player ) + { + super.close( player ); + input.close(); + } +} diff --git a/remappedSrc/dan200/computercraft/shared/computer/items/ComputerItemFactory.java b/remappedSrc/dan200/computercraft/shared/computer/items/ComputerItemFactory.java new file mode 100644 index 000000000..0b0337b01 --- /dev/null +++ b/remappedSrc/dan200/computercraft/shared/computer/items/ComputerItemFactory.java @@ -0,0 +1,43 @@ +/* + * 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.shared.computer.items; + +import dan200.computercraft.ComputerCraft; +import dan200.computercraft.shared.computer.blocks.TileComputer; +import dan200.computercraft.shared.computer.core.ComputerFamily; +import net.minecraft.item.ItemStack; + +import javax.annotation.Nonnull; + +public final class ComputerItemFactory +{ + private ComputerItemFactory() {} + + @Nonnull + public static ItemStack create( TileComputer tile ) + { + String label = tile.getLabel(); + int id = label != null ? tile.getComputerID() : -1; + return create( id, label, tile.getFamily() ); + } + + @Nonnull + public static ItemStack create( int id, String label, ComputerFamily family ) + { + switch( family ) + { + case Normal: + return ComputerCraft.Items.computerNormal.create( id, label ); + case Advanced: + return ComputerCraft.Items.computerAdvanced.create( id, label ); + case Command: + return ComputerCraft.Items.computerCommand.create( id, label ); + default: + return ItemStack.EMPTY; + } + } +} diff --git a/remappedSrc/dan200/computercraft/shared/computer/items/IComputerItem.java b/remappedSrc/dan200/computercraft/shared/computer/items/IComputerItem.java new file mode 100644 index 000000000..bf85ad668 --- /dev/null +++ b/remappedSrc/dan200/computercraft/shared/computer/items/IComputerItem.java @@ -0,0 +1,33 @@ +/* + * 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.shared.computer.items; + +import dan200.computercraft.shared.computer.core.ComputerFamily; +import net.minecraft.item.ItemStack; +import net.minecraft.nbt.CompoundTag; + +import javax.annotation.Nonnull; + +public interface IComputerItem +{ + String NBT_ID = "ComputerId"; + + default int getComputerID( @Nonnull ItemStack stack ) + { + CompoundTag nbt = stack.getTag(); + return nbt != null && nbt.contains( NBT_ID ) ? nbt.getInt( NBT_ID ) : -1; + } + + default String getLabel( @Nonnull ItemStack stack ) + { + return stack.hasCustomName() ? stack.getName().getString() : null; + } + + ComputerFamily getFamily(); + + ItemStack withFamily( @Nonnull ItemStack stack, @Nonnull ComputerFamily family ); +} diff --git a/remappedSrc/dan200/computercraft/shared/computer/items/ItemComputer.java b/remappedSrc/dan200/computercraft/shared/computer/items/ItemComputer.java new file mode 100644 index 000000000..8c1d3450f --- /dev/null +++ b/remappedSrc/dan200/computercraft/shared/computer/items/ItemComputer.java @@ -0,0 +1,38 @@ +/* + * 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.shared.computer.items; + +import dan200.computercraft.shared.computer.blocks.BlockComputer; +import dan200.computercraft.shared.computer.core.ComputerFamily; +import net.minecraft.item.ItemStack; +import net.minecraft.text.LiteralText; + +import javax.annotation.Nonnull; + +public class ItemComputer extends ItemComputerBase +{ + public ItemComputer( BlockComputer block, Settings settings ) + { + super( block, settings ); + } + + public ItemStack create( int id, String label ) + { + ItemStack result = new ItemStack( this ); + if( id >= 0 ) result.getOrCreateTag().putInt( NBT_ID, id ); + if( label != null ) result.setCustomName( new LiteralText( label ) ); + return result; + } + + @Override + public ItemStack withFamily( @Nonnull ItemStack stack, @Nonnull ComputerFamily family ) + { + ItemStack result = ComputerItemFactory.create( getComputerID( stack ), null, family ); + if( stack.hasCustomName() ) result.setCustomName( stack.getName() ); + return result; + } +} diff --git a/remappedSrc/dan200/computercraft/shared/computer/items/ItemComputerBase.java b/remappedSrc/dan200/computercraft/shared/computer/items/ItemComputerBase.java new file mode 100644 index 000000000..e0d1acd2e --- /dev/null +++ b/remappedSrc/dan200/computercraft/shared/computer/items/ItemComputerBase.java @@ -0,0 +1,94 @@ +/* + * 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.shared.computer.items; + +import dan200.computercraft.ComputerCraft; +import dan200.computercraft.api.ComputerCraftAPI; +import dan200.computercraft.api.filesystem.IMount; +import dan200.computercraft.api.media.IMedia; +import dan200.computercraft.shared.computer.blocks.BlockComputerBase; +import dan200.computercraft.shared.computer.core.ComputerFamily; +import net.minecraft.client.item.TooltipContext; +import net.minecraft.item.BlockItem; +import net.minecraft.item.ItemStack; +import net.minecraft.text.LiteralText; +import net.minecraft.text.Text; +import net.minecraft.text.TranslatableText; +import net.minecraft.util.Formatting; +import net.minecraft.world.World; + +import javax.annotation.Nonnull; +import javax.annotation.Nullable; +import java.util.List; + +public abstract class ItemComputerBase extends BlockItem implements IComputerItem, IMedia +{ + private final ComputerFamily family; + + public ItemComputerBase( BlockComputerBase block, Settings settings ) + { + super( block, settings ); + family = block.getFamily(); + } + + @Override + public void appendTooltip( @Nonnull ItemStack stack, @Nullable World world, @Nonnull List list, @Nonnull TooltipContext options ) + { + if( options.isAdvanced() ) + { + int id = getComputerID( stack ); + if( id >= 0 ) + { + list.add( new TranslatableText( "gui.computercraft.tooltip.computer_id", id ) + .formatted( Formatting.GRAY ) ); + } + } + } + + @Override + public String getLabel( @Nonnull ItemStack stack ) + { + return stack.hasCustomName() ? stack.getName().getString() : null; + } + + @Override + public final ComputerFamily getFamily() + { + return family; + } + + // IMedia implementation + + @Override + public boolean setLabel( @Nonnull ItemStack stack, String label ) + { + if( label != null ) + { + stack.setCustomName( new LiteralText( label ) ); + } + else + { + stack.removeCustomName(); + } + return true; + } + + @Override + public IMount createDataMount( @Nonnull ItemStack stack, @Nonnull World world ) + { + ComputerFamily family = getFamily(); + if( family != ComputerFamily.Command ) + { + int id = getComputerID( stack ); + if( id >= 0 ) + { + return ComputerCraftAPI.createSaveDirMount( world, "computer/" + id, ComputerCraft.computerSpaceLimit ); + } + } + return null; + } +} diff --git a/remappedSrc/dan200/computercraft/shared/computer/recipe/ComputerConvertRecipe.java b/remappedSrc/dan200/computercraft/shared/computer/recipe/ComputerConvertRecipe.java new file mode 100644 index 000000000..c5db029be --- /dev/null +++ b/remappedSrc/dan200/computercraft/shared/computer/recipe/ComputerConvertRecipe.java @@ -0,0 +1,69 @@ +/* + * 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.shared.computer.recipe; + +import dan200.computercraft.shared.computer.items.IComputerItem; +import net.minecraft.inventory.CraftingInventory; +import net.minecraft.item.ItemStack; +import net.minecraft.recipe.Ingredient; +import net.minecraft.recipe.ShapedRecipe; +import net.minecraft.util.Identifier; +import net.minecraft.util.collection.DefaultedList; +import net.minecraft.world.World; + +import javax.annotation.Nonnull; + +/** + * Represents a recipe which converts a computer from one form into another. + */ +public abstract class ComputerConvertRecipe extends ShapedRecipe +{ + private final String group; + + public ComputerConvertRecipe( Identifier identifier, String group, int width, int height, DefaultedList ingredients, ItemStack result ) + { + super( identifier, group, width, height, ingredients, result ); + this.group = group; + } + + @Nonnull + protected abstract ItemStack convert( @Nonnull IComputerItem item, @Nonnull ItemStack stack ); + + @Override + public boolean matches( @Nonnull CraftingInventory inventory, @Nonnull World world ) + { + if( !matches( inventory, world ) ) return false; + + for( int i = 0; i < inventory.size(); i++ ) + { + if( inventory.getStack( i ).getItem() instanceof IComputerItem ) return true; + } + + return false; + } + + @Nonnull + @Override + public ItemStack craft( @Nonnull CraftingInventory inventory ) + { + // Find our computer item and convert it. + for( int i = 0; i < inventory.size(); i++ ) + { + ItemStack stack = inventory.getStack( i ); + if( stack.getItem() instanceof IComputerItem ) return convert( (IComputerItem) stack.getItem(), stack ); + } + + return ItemStack.EMPTY; + } + + @Nonnull + @Override + public String getGroup() + { + return group; + } +} diff --git a/remappedSrc/dan200/computercraft/shared/computer/recipe/ComputerFamilyRecipe.java b/remappedSrc/dan200/computercraft/shared/computer/recipe/ComputerFamilyRecipe.java new file mode 100644 index 000000000..6e347d1f5 --- /dev/null +++ b/remappedSrc/dan200/computercraft/shared/computer/recipe/ComputerFamilyRecipe.java @@ -0,0 +1,81 @@ +/* + * 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.shared.computer.recipe; + +import com.google.gson.JsonObject; +import dan200.computercraft.shared.computer.core.ComputerFamily; +import dan200.computercraft.shared.util.RecipeUtil; +import net.minecraft.item.ItemStack; +import net.minecraft.network.PacketByteBuf; +import net.minecraft.recipe.Ingredient; +import net.minecraft.recipe.RecipeSerializer; +import net.minecraft.recipe.ShapedRecipe; +import net.minecraft.util.Identifier; +import net.minecraft.util.JsonHelper; +import net.minecraft.util.collection.DefaultedList; +import javax.annotation.Nonnull; + +public abstract class ComputerFamilyRecipe extends ComputerConvertRecipe +{ + private final ComputerFamily family; + + public ComputerFamilyRecipe( Identifier identifier, String group, int width, int height, DefaultedList ingredients, ItemStack result, ComputerFamily family ) + { + super( identifier, group, width, height, ingredients, result ); + this.family = family; + } + + public ComputerFamily getFamily() + { + return family; + } + + public abstract static class Serializer implements RecipeSerializer + { + protected abstract T create( Identifier identifier, String group, int width, int height, DefaultedList ingredients, ItemStack result, ComputerFamily family ); + + @Nonnull + @Override + public T read( @Nonnull Identifier identifier, @Nonnull JsonObject json ) + { + String group = JsonHelper.getString( json, "group", "" ); + ComputerFamily family = RecipeUtil.getFamily( json, "family" ); + + RecipeUtil.ShapedTemplate template = RecipeUtil.getTemplate( json ); + ItemStack result = ShapedRecipe.getItemStack( JsonHelper.getObject( json, "result" ) ); + + return create( identifier, group, template.width, template.height, template.ingredients, result, family ); + } + + @Nonnull + @Override + public T read( @Nonnull Identifier identifier, @Nonnull PacketByteBuf buf ) + { + int width = buf.readVarInt(); + int height = buf.readVarInt(); + String group = buf.readString( Short.MAX_VALUE ); + + DefaultedList ingredients = DefaultedList.ofSize( width * height, Ingredient.EMPTY ); + for( int i = 0; i < ingredients.size(); i++ ) ingredients.set( i, Ingredient.fromPacket( buf ) ); + + ItemStack result = buf.readItemStack(); + ComputerFamily family = buf.readEnumConstant( ComputerFamily.class ); + return create( identifier, group, width, height, ingredients, result, family ); + } + + @Override + public void write( @Nonnull PacketByteBuf buf, @Nonnull T recipe ) + { + buf.writeVarInt( recipe.getWidth() ); + buf.writeVarInt( recipe.getHeight() ); + buf.writeString( recipe.getGroup() ); + for( Ingredient ingredient : recipe.getPreviewInputs() ) ingredient.write( buf ); + buf.writeItemStack( recipe.getOutput() ); + buf.writeEnumConstant( recipe.getFamily() ); + } + } +} diff --git a/remappedSrc/dan200/computercraft/shared/computer/recipe/ComputerUpgradeRecipe.java b/remappedSrc/dan200/computercraft/shared/computer/recipe/ComputerUpgradeRecipe.java new file mode 100644 index 000000000..64167c031 --- /dev/null +++ b/remappedSrc/dan200/computercraft/shared/computer/recipe/ComputerUpgradeRecipe.java @@ -0,0 +1,47 @@ +/* + * 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.shared.computer.recipe; + +import dan200.computercraft.shared.computer.core.ComputerFamily; +import dan200.computercraft.shared.computer.items.IComputerItem; +import net.minecraft.item.ItemStack; +import net.minecraft.recipe.Ingredient; +import net.minecraft.recipe.RecipeSerializer; +import net.minecraft.util.Identifier; +import net.minecraft.util.collection.DefaultedList; +import javax.annotation.Nonnull; + +public class ComputerUpgradeRecipe extends ComputerFamilyRecipe +{ + public ComputerUpgradeRecipe( Identifier identifier, String group, int width, int height, DefaultedList ingredients, ItemStack result, ComputerFamily family ) + { + super( identifier, group, width, height, ingredients, result, family ); + } + + @Nonnull + @Override + protected ItemStack convert( @Nonnull IComputerItem item, @Nonnull ItemStack stack ) + { + return item.withFamily( stack, getFamily() ); + } + + @Nonnull + @Override + public RecipeSerializer getSerializer() + { + return SERIALIZER; + } + + public static final RecipeSerializer SERIALIZER = new dan200.computercraft.shared.computer.recipe.ComputerFamilyRecipe.Serializer() + { + @Override + protected ComputerUpgradeRecipe create( Identifier identifier, String group, int width, int height, DefaultedList ingredients, ItemStack result, ComputerFamily family ) + { + return new ComputerUpgradeRecipe( identifier, group, width, height, ingredients, result, family ); + } + }; +} diff --git a/remappedSrc/dan200/computercraft/shared/integration/charset/BundledCapabilityProvider.java b/remappedSrc/dan200/computercraft/shared/integration/charset/BundledCapabilityProvider.java new file mode 100644 index 000000000..9a2f8aa3c --- /dev/null +++ b/remappedSrc/dan200/computercraft/shared/integration/charset/BundledCapabilityProvider.java @@ -0,0 +1,90 @@ +/* + * 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.shared.integration.charset; + +import dan200.computercraft.shared.common.TileGeneric; +import net.minecraft.util.Direction; +import net.minecraftforge.common.capabilities.Capability; +import net.minecraftforge.common.capabilities.ICapabilityProvider; +import pl.asie.charset.api.wires.IBundledEmitter; +import pl.asie.charset.api.wires.IBundledReceiver; + +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +import static dan200.computercraft.shared.integration.charset.IntegrationCharset.CAPABILITY_EMITTER; +import static dan200.computercraft.shared.integration.charset.IntegrationCharset.CAPABILITY_RECEIVER; + +final class BundledCapabilityProvider implements ICapabilityProvider +{ + private final TileGeneric tile; + private IBundledReceiver receiver; + private IBundledEmitter[] emitters; + + BundledCapabilityProvider( TileGeneric tile ) + { + this.tile = tile; + } + + @Override + public boolean hasCapability( @Nonnull Capability capability, @Nullable Direction side ) + { + return capability == CAPABILITY_EMITTER || capability == CAPABILITY_RECEIVER; + } + + @Nullable + @Override + public T getCapability( @Nonnull Capability capability, @Nullable Direction side ) + { + if( capability == CAPABILITY_RECEIVER ) + { + IBundledReceiver receiver = this.receiver; + if( receiver == null ) + { + receiver = this.receiver = () -> tile.onNeighbourChange( tile.getPos().offset( side ) ); + } + + return CAPABILITY_RECEIVER.cast( receiver ); + } + else if( capability == CAPABILITY_EMITTER ) + { + IBundledEmitter[] emitters = this.emitters; + if( emitters == null ) emitters = this.emitters = new IBundledEmitter[7]; + + int index = side == null ? 6 : side.getIndex(); + IBundledEmitter emitter = emitters[index]; + if( emitter == null ) + { + if( side == null ) + { + emitter = emitters[index] = () -> { + int flags = 0; + for( Direction facing : Direction.VALUES ) flags |= tile.getBundledRedstoneOutput( facing ); + return toBytes( flags ); + }; + } + else + { + emitter = emitters[index] = () -> toBytes( tile.getBundledRedstoneOutput( side ) ); + } + } + + return CAPABILITY_EMITTER.cast( emitter ); + } + else + { + return null; + } + } + + private static byte[] toBytes( int flag ) + { + byte[] channels = new byte[16]; + for( int i = 0; i < 16; i++ ) channels[i] = (flag & (1 << i)) == 0 ? (byte) 0 : 15; + return channels; + } +} diff --git a/remappedSrc/dan200/computercraft/shared/integration/charset/BundledRedstoneProvider.java b/remappedSrc/dan200/computercraft/shared/integration/charset/BundledRedstoneProvider.java new file mode 100644 index 000000000..c64cc7f64 --- /dev/null +++ b/remappedSrc/dan200/computercraft/shared/integration/charset/BundledRedstoneProvider.java @@ -0,0 +1,34 @@ +/* + * 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.shared.integration.charset; + +import dan200.computercraft.api.redstone.IBundledRedstoneProvider; +import net.minecraft.tileentity.TileEntity; +import net.minecraft.util.Direction; +import net.minecraft.util.math.BlockPos; +import net.minecraft.world.World; + +import javax.annotation.Nonnull; + +import static dan200.computercraft.shared.integration.charset.IntegrationCharset.CAPABILITY_EMITTER; + +public class BundledRedstoneProvider implements IBundledRedstoneProvider +{ + @Override + public int getBundledRedstoneOutput( @Nonnull World world, @Nonnull BlockPos pos, @Nonnull Direction side ) + { + TileEntity tile = world.getTileEntity( pos ); + if( tile == null || !tile.hasCapability( CAPABILITY_EMITTER, side ) ) return -1; + + byte[] signal = tile.getCapability( CAPABILITY_EMITTER, side ).getBundledSignal(); + if( signal == null ) return -1; + + int flag = 0; + for( int i = 0; i < signal.length; i++ ) flag |= signal[i] > 0 ? 1 << i : 0; + return flag; + } +} diff --git a/remappedSrc/dan200/computercraft/shared/integration/charset/IntegrationCharset.java b/remappedSrc/dan200/computercraft/shared/integration/charset/IntegrationCharset.java new file mode 100644 index 000000000..fec2e8959 --- /dev/null +++ b/remappedSrc/dan200/computercraft/shared/integration/charset/IntegrationCharset.java @@ -0,0 +1,53 @@ +/* + * 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.shared.integration.charset; + +import dan200.computercraft.ComputerCraft; +import dan200.computercraft.api.ComputerCraftAPI; +import dan200.computercraft.shared.common.TileGeneric; +import net.minecraft.tileentity.TileEntity; +import net.minecraft.util.ResourceLocation; +import net.minecraftforge.common.MinecraftForge; +import net.minecraftforge.common.capabilities.Capability; +import net.minecraftforge.common.capabilities.CapabilityInject; +import net.minecraftforge.event.AttachCapabilitiesEvent; +import net.minecraftforge.fml.common.eventhandler.SubscribeEvent; +import pl.asie.charset.api.wires.IBundledEmitter; +import pl.asie.charset.api.wires.IBundledReceiver; + +public final class IntegrationCharset +{ + private static final ResourceLocation CAPABILITY_KEY = new ResourceLocation( ComputerCraft.MOD_ID, "charset" ); + + @CapabilityInject( IBundledEmitter.class ) + static Capability CAPABILITY_EMITTER = null; + + @CapabilityInject( IBundledReceiver.class ) + static Capability CAPABILITY_RECEIVER = null; + + private IntegrationCharset() + { + } + + public static void register() + { + if( CAPABILITY_EMITTER == null || CAPABILITY_RECEIVER == null ) return; + + MinecraftForge.EVENT_BUS.register( IntegrationCharset.class ); + ComputerCraftAPI.registerBundledRedstoneProvider( new BundledRedstoneProvider() ); + } + + @SubscribeEvent + public static void attachGenericCapabilities( AttachCapabilitiesEvent event ) + { + TileEntity tile = event.getObject(); + if( tile instanceof TileGeneric ) + { + event.addCapability( CAPABILITY_KEY, new BundledCapabilityProvider( (TileGeneric) tile ) ); + } + } +} diff --git a/remappedSrc/dan200/computercraft/shared/integration/jei/JEIComputerCraft.java b/remappedSrc/dan200/computercraft/shared/integration/jei/JEIComputerCraft.java new file mode 100644 index 000000000..2ce9eb8e6 --- /dev/null +++ b/remappedSrc/dan200/computercraft/shared/integration/jei/JEIComputerCraft.java @@ -0,0 +1,164 @@ +/* + * 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.shared.integration.jei; + +import dan200.computercraft.ComputerCraft; +import dan200.computercraft.api.pocket.IPocketUpgrade; +import dan200.computercraft.api.turtle.ITurtleUpgrade; +import dan200.computercraft.api.turtle.TurtleSide; +import dan200.computercraft.shared.PocketUpgrades; +import dan200.computercraft.shared.TurtleUpgrades; +import dan200.computercraft.shared.computer.core.ComputerFamily; +import dan200.computercraft.shared.media.items.ItemDisk; +import dan200.computercraft.shared.pocket.items.ItemPocketComputer; +import dan200.computercraft.shared.pocket.items.PocketComputerItemFactory; +import dan200.computercraft.shared.turtle.items.ITurtleItem; +import dan200.computercraft.shared.turtle.items.TurtleItemFactory; +import mezz.jei.api.IModPlugin; +import mezz.jei.api.JeiPlugin; +import mezz.jei.api.constants.VanillaRecipeCategoryUid; +import mezz.jei.api.constants.VanillaTypes; +import mezz.jei.api.ingredients.subtypes.ISubtypeInterpreter; +import mezz.jei.api.recipe.IRecipeManager; +import mezz.jei.api.recipe.category.IRecipeCategory; +import mezz.jei.api.registration.IAdvancedRegistration; +import mezz.jei.api.registration.ISubtypeRegistration; +import mezz.jei.api.runtime.IJeiRuntime; +import net.minecraft.item.Item; +import net.minecraft.item.ItemStack; +import net.minecraft.item.crafting.IRecipe; +import net.minecraft.util.ResourceLocation; + +import javax.annotation.Nonnull; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +import static dan200.computercraft.shared.integration.jei.RecipeResolver.MAIN_FAMILIES; + +@JeiPlugin +public class JEIComputerCraft implements IModPlugin +{ + @Nonnull + @Override + public ResourceLocation getPluginUid() + { + return new ResourceLocation( ComputerCraft.MOD_ID, "jei" ); + } + + @Override + public void registerItemSubtypes( ISubtypeRegistration subtypeRegistry ) + { + subtypeRegistry.registerSubtypeInterpreter( ComputerCraft.Items.turtleNormal, turtleSubtype ); + subtypeRegistry.registerSubtypeInterpreter( ComputerCraft.Items.turtleAdvanced, turtleSubtype ); + + subtypeRegistry.registerSubtypeInterpreter( ComputerCraft.Items.pocketComputerNormal, pocketSubtype ); + subtypeRegistry.registerSubtypeInterpreter( ComputerCraft.Items.pocketComputerAdvanced, pocketSubtype ); + + subtypeRegistry.registerSubtypeInterpreter( ComputerCraft.Items.disk, diskSubtype ); + } + + @Override + public void registerAdvanced( IAdvancedRegistration registry ) + { + registry.addRecipeManagerPlugin( new RecipeResolver() ); + } + + @Override + public void onRuntimeAvailable( IJeiRuntime runtime ) + { + IRecipeManager registry = runtime.getRecipeManager(); + + // Register all turtles/pocket computers (not just vanilla upgrades) as upgrades on JEI. + List upgradeItems = new ArrayList<>(); + for( ComputerFamily family : MAIN_FAMILIES ) + { + for( ITurtleUpgrade upgrade : TurtleUpgrades.getUpgrades() ) + { + if( !TurtleUpgrades.suitableForFamily( family, upgrade ) ) continue; + + upgradeItems.add( TurtleItemFactory.create( -1, null, -1, family, null, upgrade, 0, null ) ); + } + + for( IPocketUpgrade upgrade : PocketUpgrades.getUpgrades() ) + { + upgradeItems.add( PocketComputerItemFactory.create( -1, null, -1, family, upgrade ) ); + } + } + + runtime.getIngredientManager().addIngredientsAtRuntime( VanillaTypes.ITEM, upgradeItems ); + + // Hide treasure disks + runtime.getIngredientManager().removeIngredientsAtRuntime( VanillaTypes.ITEM, + Collections.singletonList( new ItemStack( ComputerCraft.Items.treasureDisk ) ) ); + + // Hide all upgrade recipes + IRecipeCategory category = (IRecipeCategory) registry.getRecipeCategory( VanillaRecipeCategoryUid.CRAFTING ); + if( category != null ) + { + for( Object wrapper : registry.getRecipes( category ) ) + { + if( !(wrapper instanceof IRecipe) ) continue; + ResourceLocation id = ((IRecipe) wrapper).getId(); + if( id.getNamespace().equals( ComputerCraft.MOD_ID ) + && (id.getPath().startsWith( "generated/turtle_" ) || id.getPath().startsWith( "generated/pocket_" )) ) + { + registry.hideRecipe( wrapper, VanillaRecipeCategoryUid.CRAFTING ); + } + } + } + } + + /** + * Distinguishes turtles by upgrades and family + */ + private static final ISubtypeInterpreter turtleSubtype = stack -> { + Item item = stack.getItem(); + if( !(item instanceof ITurtleItem) ) return ""; + + ITurtleItem turtle = (ITurtleItem) item; + StringBuilder name = new StringBuilder(); + + // Add left and right upgrades to the identifier + ITurtleUpgrade left = turtle.getUpgrade( stack, TurtleSide.Left ); + ITurtleUpgrade right = turtle.getUpgrade( stack, TurtleSide.Right ); + if( left != null ) name.append( left.getUpgradeID() ); + if( left != null && right != null ) name.append( '|' ); + if( right != null ) name.append( right.getUpgradeID() ); + + return name.toString(); + }; + + /** + * Distinguishes pocket computers by upgrade and family + */ + private static final ISubtypeInterpreter pocketSubtype = stack -> { + Item item = stack.getItem(); + if( !(item instanceof ItemPocketComputer) ) return ""; + + StringBuilder name = new StringBuilder(); + + // Add the upgrade to the identifier + IPocketUpgrade upgrade = ItemPocketComputer.getUpgrade( stack ); + if( upgrade != null ) name.append( upgrade.getUpgradeID() ); + + return name.toString(); + }; + + /** + * Distinguishes disks by colour + */ + private static final ISubtypeInterpreter diskSubtype = stack -> { + Item item = stack.getItem(); + if( !(item instanceof ItemDisk) ) return ""; + + ItemDisk disk = (ItemDisk) item; + + int colour = disk.getColour( stack ); + return colour == -1 ? "" : String.format( "%06x", colour ); + }; +} diff --git a/remappedSrc/dan200/computercraft/shared/integration/jei/RecipeResolver.java b/remappedSrc/dan200/computercraft/shared/integration/jei/RecipeResolver.java new file mode 100644 index 000000000..e30417122 --- /dev/null +++ b/remappedSrc/dan200/computercraft/shared/integration/jei/RecipeResolver.java @@ -0,0 +1,404 @@ +/* + * 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.shared.integration.jei; + +import dan200.computercraft.ComputerCraft; +import dan200.computercraft.api.pocket.IPocketUpgrade; +import dan200.computercraft.api.turtle.ITurtleUpgrade; +import dan200.computercraft.api.turtle.TurtleSide; +import dan200.computercraft.shared.PocketUpgrades; +import dan200.computercraft.shared.TurtleUpgrades; +import dan200.computercraft.shared.computer.core.ComputerFamily; +import dan200.computercraft.shared.pocket.items.ItemPocketComputer; +import dan200.computercraft.shared.pocket.items.PocketComputerItemFactory; +import dan200.computercraft.shared.turtle.items.ITurtleItem; +import dan200.computercraft.shared.turtle.items.TurtleItemFactory; +import dan200.computercraft.shared.util.InventoryUtil; +import mezz.jei.api.constants.VanillaRecipeCategoryUid; +import mezz.jei.api.recipe.IFocus; +import mezz.jei.api.recipe.advanced.IRecipeManagerPlugin; +import mezz.jei.api.recipe.category.IRecipeCategory; +import net.minecraft.item.Item; +import net.minecraft.item.ItemStack; +import net.minecraft.item.crafting.IRecipeSerializer; +import net.minecraft.item.crafting.Ingredient; +import net.minecraft.item.crafting.ShapedRecipe; +import net.minecraft.util.NonNullList; +import net.minecraft.util.ResourceLocation; + +import javax.annotation.Nonnull; +import java.util.*; + +import static net.minecraft.item.crafting.Ingredient.fromStacks; +import static net.minecraft.util.NonNullList.from; + +class RecipeResolver implements IRecipeManagerPlugin +{ + static final ComputerFamily[] MAIN_FAMILIES = new ComputerFamily[] { ComputerFamily.Normal, ComputerFamily.Advanced }; + + private final Map> upgradeItemLookup = new HashMap<>(); + private final List pocketUpgrades = new ArrayList<>(); + private final List turtleUpgrades = new ArrayList<>(); + private boolean initialised = false; + + /** + * Build a cache of items which are used for turtle and pocket computer upgrades. + */ + private void setupCache() + { + if( initialised ) return; + initialised = true; + + for( ITurtleUpgrade upgrade : TurtleUpgrades.getUpgrades() ) + { + ItemStack stack = upgrade.getCraftingItem(); + if( stack.isEmpty() ) continue; + + UpgradeInfo info = new UpgradeInfo( stack, upgrade ); + upgradeItemLookup.computeIfAbsent( stack.getItem(), k -> new ArrayList<>( 1 ) ).add( info ); + turtleUpgrades.add( info ); + } + + for( IPocketUpgrade upgrade : PocketUpgrades.getUpgrades() ) + { + ItemStack stack = upgrade.getCraftingItem(); + if( stack.isEmpty() ) continue; + + UpgradeInfo info = new UpgradeInfo( stack, upgrade ); + upgradeItemLookup.computeIfAbsent( stack.getItem(), k -> new ArrayList<>( 1 ) ).add( info ); + pocketUpgrades.add( info ); + } + } + + private boolean hasUpgrade( @Nonnull ItemStack stack ) + { + if( stack.isEmpty() ) return false; + + setupCache(); + List upgrades = upgradeItemLookup.get( stack.getItem() ); + if( upgrades == null ) return false; + + for( UpgradeInfo upgrade : upgrades ) + { + ItemStack craftingStack = upgrade.stack; + if( !craftingStack.isEmpty() && InventoryUtil.areItemsSimilar( stack, craftingStack ) ) return true; + } + + return false; + } + + @Nonnull + @Override + public List getRecipeCategoryUids( @Nonnull IFocus focus ) + { + V value = focus.getValue(); + if( !(value instanceof ItemStack) ) return Collections.emptyList(); + + ItemStack stack = (ItemStack) value; + switch( focus.getMode() ) + { + case INPUT: + return stack.getItem() instanceof ITurtleItem || stack.getItem() instanceof ItemPocketComputer || + hasUpgrade( stack ) + ? Collections.singletonList( VanillaRecipeCategoryUid.CRAFTING ) + : Collections.emptyList(); + case OUTPUT: + return stack.getItem() instanceof ITurtleItem || stack.getItem() instanceof ItemPocketComputer + ? Collections.singletonList( VanillaRecipeCategoryUid.CRAFTING ) + : Collections.emptyList(); + default: + return Collections.emptyList(); + } + } + + @Nonnull + @Override + public List getRecipes( @Nonnull IRecipeCategory recipeCategory, @Nonnull IFocus focus ) + { + if( !(focus.getValue() instanceof ItemStack) || !recipeCategory.getUid().equals( VanillaRecipeCategoryUid.CRAFTING ) ) + { + return Collections.emptyList(); + } + + ItemStack stack = (ItemStack) focus.getValue(); + switch( focus.getMode() ) + { + case INPUT: + return cast( findRecipesWithInput( stack ) ); + case OUTPUT: + return cast( findRecipesWithOutput( stack ) ); + default: + return Collections.emptyList(); + } + } + + @Nonnull + @Override + public List getRecipes( @Nonnull IRecipeCategory recipeCategory ) + { + return Collections.emptyList(); + } + + @Nonnull + private List findRecipesWithInput( @Nonnull ItemStack stack ) + { + setupCache(); + + if( stack.getItem() instanceof ITurtleItem ) + { + // Suggest possible upgrades which can be applied to this turtle + ITurtleItem item = (ITurtleItem) stack.getItem(); + ITurtleUpgrade left = item.getUpgrade( stack, TurtleSide.Left ); + ITurtleUpgrade right = item.getUpgrade( stack, TurtleSide.Right ); + if( left != null && right != null ) return Collections.emptyList(); + + List recipes = new ArrayList<>(); + Ingredient ingredient = fromStacks( stack ); + for( UpgradeInfo upgrade : turtleUpgrades ) + { + // The turtle is facing towards us, so upgrades on the left are actually crafted on the right. + if( left == null ) + { + recipes.add( horizontal( from( Ingredient.EMPTY, ingredient, upgrade.ingredient ), turtleWith( stack, upgrade.turtle, right ) ) ); + } + + if( right == null ) + { + recipes.add( horizontal( from( Ingredient.EMPTY, upgrade.ingredient, ingredient ), turtleWith( stack, left, upgrade.turtle ) ) ); + } + } + + return cast( recipes ); + } + else if( stack.getItem() instanceof ItemPocketComputer ) + { + // Suggest possible upgrades which can be applied to this turtle + IPocketUpgrade back = ItemPocketComputer.getUpgrade( stack ); + if( back != null ) return Collections.emptyList(); + + List recipes = new ArrayList<>(); + Ingredient ingredient = fromStacks( stack ); + for( UpgradeInfo upgrade : pocketUpgrades ) + { + recipes.add( vertical( from( Ingredient.EMPTY, ingredient, upgrade.ingredient ), pocketWith( stack, upgrade.pocket ) ) ); + } + + return recipes; + } + else + { + List upgrades = upgradeItemLookup.get( stack.getItem() ); + if( upgrades == null ) return Collections.emptyList(); + + List recipes = null; + boolean multiple = false; + Ingredient ingredient = fromStacks( stack ); + for( UpgradeInfo upgrade : upgrades ) + { + ItemStack craftingStack = upgrade.stack; + if( craftingStack.isEmpty() || !InventoryUtil.areItemsSimilar( stack, craftingStack ) ) + { + continue; + } + + if( recipes == null ) + { + recipes = upgrade.getRecipes(); + } + else + { + if( !multiple ) + { + multiple = true; + recipes = new ArrayList<>( recipes ); + } + recipes.addAll( upgrade.getRecipes() ); + } + } + + return recipes == null ? Collections.emptyList() : recipes; + } + } + + @Nonnull + private static List findRecipesWithOutput( @Nonnull ItemStack stack ) + { + // Find which upgrade this item currently has, an so how we could build it. + if( stack.getItem() instanceof ITurtleItem ) + { + ITurtleItem item = (ITurtleItem) stack.getItem(); + List recipes = new ArrayList<>( 0 ); + + ITurtleUpgrade left = item.getUpgrade( stack, TurtleSide.Left ); + ITurtleUpgrade right = item.getUpgrade( stack, TurtleSide.Right ); + + // The turtle is facing towards us, so upgrades on the left are actually crafted on the right. + if( left != null ) + { + recipes.add( horizontal( + from( Ingredient.EMPTY, fromStacks( turtleWith( stack, null, right ) ), fromStacks( left.getCraftingItem() ) ), + stack + ) ); + } + + if( right != null ) + { + recipes.add( horizontal( + from( Ingredient.EMPTY, fromStacks( right.getCraftingItem() ), fromStacks( turtleWith( stack, left, null ) ) ), + stack + ) ); + } + + return cast( recipes ); + } + else if( stack.getItem() instanceof ItemPocketComputer ) + { + List recipes = new ArrayList<>( 0 ); + + IPocketUpgrade back = ItemPocketComputer.getUpgrade( stack ); + if( back != null ) + { + recipes.add( vertical( + from( Ingredient.EMPTY, fromStacks( back.getCraftingItem() ), fromStacks( pocketWith( stack, null ) ) ), + stack + ) ); + } + + return cast( recipes ); + } + else + { + return Collections.emptyList(); + } + } + + @SuppressWarnings( { "unchecked", "rawtypes" } ) + private static List cast( List from ) + { + return (List) from; + } + + private static ItemStack turtleWith( ItemStack stack, ITurtleUpgrade left, ITurtleUpgrade right ) + { + ITurtleItem item = (ITurtleItem) stack.getItem(); + return TurtleItemFactory.create( + item.getComputerID( stack ), item.getLabel( stack ), item.getColour( stack ), item.getFamily(), + left, right, item.getFuelLevel( stack ), item.getOverlay( stack ) + ); + } + + private static ItemStack pocketWith( ItemStack stack, IPocketUpgrade back ) + { + ItemPocketComputer item = (ItemPocketComputer) stack.getItem(); + return PocketComputerItemFactory.create( + item.getComputerID( stack ), item.getLabel( stack ), item.getColour( stack ), item.getFamily(), + back + ); + } + + private static Shaped vertical( NonNullList input, ItemStack result ) + { + return new Shaped( 1, input.size(), input, result ); + } + + private static Shaped horizontal( NonNullList input, ItemStack result ) + { + return new Shaped( input.size(), 1, input, result ); + } + + private static class Shaped extends ShapedRecipe + { + private static final ResourceLocation ID = new ResourceLocation( ComputerCraft.MOD_ID, "impostor" ); + + Shaped( int width, int height, NonNullList input, ItemStack output ) + { + super( ID, null, width, height, input, output ); + } + + @Nonnull + @Override + public ResourceLocation getId() + { + return null; + } + + @Nonnull + @Override + public IRecipeSerializer getSerializer() + { + throw new IllegalStateException( "Should not serialise the JEI recipe" ); + } + } + + private static final class Upgrade + { + final T upgrade; + final ItemStack stack; + final Ingredient ingredient; + + private Upgrade( T upgrade, ItemStack stack ) + { + this.upgrade = upgrade; + this.stack = stack; + ingredient = fromStacks( stack ); + } + } + + private static class UpgradeInfo + { + final ItemStack stack; + final Ingredient ingredient; + final ITurtleUpgrade turtle; + final IPocketUpgrade pocket; + ArrayList recipes; + + UpgradeInfo( ItemStack stack, ITurtleUpgrade turtle ) + { + this.stack = stack; + ingredient = fromStacks( stack ); + this.turtle = turtle; + pocket = null; + } + + UpgradeInfo( ItemStack stack, IPocketUpgrade pocket ) + { + this.stack = stack; + ingredient = fromStacks( stack ); + turtle = null; + this.pocket = pocket; + } + + List getRecipes() + { + ArrayList recipes = this.recipes; + if( recipes != null ) return recipes; + + recipes = this.recipes = new ArrayList<>( 4 ); + for( ComputerFamily family : MAIN_FAMILIES ) + { + if( turtle != null && TurtleUpgrades.suitableForFamily( family, turtle ) ) + { + recipes.add( horizontal( + from( Ingredient.EMPTY, ingredient, fromStacks( TurtleItemFactory.create( -1, null, -1, family, null, null, 0, null ) ) ), + TurtleItemFactory.create( -1, null, -1, family, null, turtle, 0, null ) + ) ); + } + + if( pocket != null ) + { + recipes.add( vertical( + from( Ingredient.EMPTY, ingredient, fromStacks( PocketComputerItemFactory.create( -1, null, -1, family, null ) ) ), + PocketComputerItemFactory.create( -1, null, -1, family, pocket ) + ) ); + } + } + + recipes.trimToSize(); + return recipes; + } + } +} diff --git a/remappedSrc/dan200/computercraft/shared/integration/mcmp/MCMPHooks.java b/remappedSrc/dan200/computercraft/shared/integration/mcmp/MCMPHooks.java new file mode 100644 index 000000000..5c4c810a1 --- /dev/null +++ b/remappedSrc/dan200/computercraft/shared/integration/mcmp/MCMPHooks.java @@ -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.shared.integration.mcmp; + +import mcmultipart.MCMultiPart; +import mcmultipart.api.item.ItemBlockMultipart; +import net.minecraft.block.state.IBlockState; +import net.minecraft.entity.player.PlayerEntity; +import net.minecraft.item.ItemBlock; +import net.minecraft.item.ItemStack; +import net.minecraft.util.Direction; +import net.minecraft.util.EnumActionResult; +import net.minecraft.util.EnumHand; +import net.minecraft.util.math.BlockPos; +import net.minecraft.world.World; +import net.minecraftforge.fml.common.Loader; + +import javax.annotation.Nonnull; + +public final class MCMPHooks +{ + private MCMPHooks() + { + } + + public static EnumActionResult onItemUse( ItemBlock itemBlock, PlayerEntity player, World world, @Nonnull BlockPos pos, @Nonnull EnumHand hand, @Nonnull Direction facing, float hitX, float hitY, float hitZ ) + { + if( !Loader.isModLoaded( MCMultiPart.MODID ) ) return EnumActionResult.PASS; + + return ItemBlockMultipart.place( + player, world, pos, hand, facing, hitX, hitY, hitZ, itemBlock, + itemBlock.getBlock()::getStateForPlacement, + MCMPIntegration.multipartMap.get( itemBlock.getBlock() ), + + ( + ItemStack stack, PlayerEntity thisPlayer, World thisWorld, BlockPos thisPos, Direction thisFacing, + float thisX, float thisY, float thisZ, IBlockState thisState + ) -> + thisPlayer.canPlayerEdit( thisPos, thisFacing, stack ) && + thisWorld.getBlockState( thisPos ).getBlock().isReplaceable( thisWorld, thisPos ) && + itemBlock.getBlock().canPlaceBlockAt( thisWorld, thisPos ) && + itemBlock.getBlock().canPlaceBlockOnSide( thisWorld, thisPos, thisFacing ) && + itemBlock.placeBlockAt( stack, thisPlayer, thisWorld, thisPos, thisFacing, thisX, thisY, thisZ, thisState ), + ItemBlockMultipart::placePartAt + ); + } +} diff --git a/remappedSrc/dan200/computercraft/shared/integration/mcmp/MCMPIntegration.java b/remappedSrc/dan200/computercraft/shared/integration/mcmp/MCMPIntegration.java new file mode 100644 index 000000000..d3031c695 --- /dev/null +++ b/remappedSrc/dan200/computercraft/shared/integration/mcmp/MCMPIntegration.java @@ -0,0 +1,127 @@ +/* + * 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.shared.integration.mcmp; + +import dan200.computercraft.ComputerCraft; +import dan200.computercraft.api.ComputerCraftAPI; +import dan200.computercraft.api.peripheral.IPeripheral; +import dan200.computercraft.api.peripheral.IPeripheralTile; +import dan200.computercraft.shared.peripheral.common.TilePeripheralBase; +import dan200.computercraft.shared.peripheral.modem.wireless.TileAdvancedModem; +import dan200.computercraft.shared.peripheral.modem.wireless.TileWirelessModem; +import dan200.computercraft.shared.peripheral.monitor.TileMonitor; +import mcmultipart.api.addon.IMCMPAddon; +import mcmultipart.api.addon.MCMPAddon; +import mcmultipart.api.container.IMultipartContainer; +import mcmultipart.api.multipart.IMultipart; +import mcmultipart.api.multipart.IMultipartRegistry; +import mcmultipart.api.multipart.IMultipartTile; +import mcmultipart.api.ref.MCMPCapabilities; +import mcmultipart.api.slot.EnumFaceSlot; +import net.minecraft.block.Block; +import net.minecraft.tileentity.TileEntity; +import net.minecraft.util.Direction; +import net.minecraft.util.ResourceLocation; +import net.minecraftforge.common.MinecraftForge; +import net.minecraftforge.common.capabilities.Capability; +import net.minecraftforge.common.capabilities.ICapabilityProvider; +import net.minecraftforge.event.AttachCapabilitiesEvent; +import net.minecraftforge.fml.common.eventhandler.SubscribeEvent; + +import javax.annotation.Nonnull; +import javax.annotation.Nullable; +import java.util.HashMap; +import java.util.Map; + +@MCMPAddon +public class MCMPIntegration implements IMCMPAddon +{ + private static final ResourceLocation CAPABILITY_KEY = new ResourceLocation( ComputerCraft.MOD_ID, "mcmultipart" ); + + static final Map multipartMap = new HashMap<>(); + + private static void register( IMultipartRegistry registry, Block block, IMultipart multipart ) + { + registry.registerPartWrapper( block, multipart ); + multipartMap.put( block, multipart ); + } + + @Override + public void registerParts( IMultipartRegistry registry ) + { + // Setup all parts + register( registry, ComputerCraft.Blocks.peripheral, new PartPeripheral() ); + register( registry, ComputerCraft.Blocks.advancedModem, new PartAdvancedModem() ); + + // Subscribe to capability events + MinecraftForge.EVENT_BUS.register( MCMPIntegration.class ); + + // Register a peripheral provider + ComputerCraftAPI.registerPeripheralProvider( ( world, pos, side ) -> + { + TileEntity tile = world.getTileEntity( pos ); + if( tile == null || !tile.hasCapability( MCMPCapabilities.MULTIPART_CONTAINER, null ) ) return null; + IMultipartContainer container = tile.getCapability( MCMPCapabilities.MULTIPART_CONTAINER, null ); + if( container == null ) return null; + + IMultipartTile multipart = container.getPartTile( EnumFaceSlot.fromFace( side ) ).orElse( null ); + if( multipart == null ) return null; + if( multipart instanceof IPeripheral ) return (IPeripheral) multipart; + if( multipart instanceof IPeripheralTile ) return ((IPeripheralTile) multipart).getPeripheral( side ); + + TileEntity underlying = multipart.getTileEntity(); + if( underlying instanceof IPeripheral ) return (IPeripheral) underlying; + if( underlying instanceof IPeripheralTile ) return ((IPeripheralTile) underlying).getPeripheral( side ); + + return null; + } ); + } + + @SubscribeEvent + public static void attach( AttachCapabilitiesEvent event ) + { + TileEntity tile = event.getObject(); + if( tile instanceof TileAdvancedModem || tile instanceof TileWirelessModem + || tile instanceof TilePeripheralBase || tile instanceof TileMonitor ) + { + // We need to attach to modems (obviously), but also any other tile created by BlockPeripheral. Otherwise + // IMultipart.convertToMultipartTile will error. + event.addCapability( CAPABILITY_KEY, new BasicMultipart( tile ) ); + } + } + + private static final class BasicMultipart implements ICapabilityProvider + { + private final TileEntity tile; + private IMultipartTile wrapped; + + private BasicMultipart( TileEntity tile ) + { + this.tile = tile; + } + + @Override + public boolean hasCapability( @Nonnull Capability capability, @Nullable Direction facing ) + { + return capability == MCMPCapabilities.MULTIPART_TILE; + } + + @Nullable + @Override + public T getCapability( @Nonnull Capability capability, @Nullable Direction facing ) + { + if( capability == MCMPCapabilities.MULTIPART_TILE ) + { + IMultipartTile wrapped = this.wrapped; + if( wrapped == null ) wrapped = this.wrapped = IMultipartTile.wrap( tile ); + return MCMPCapabilities.MULTIPART_TILE.cast( wrapped ); + } + + return null; + } + } +} diff --git a/remappedSrc/dan200/computercraft/shared/integration/mcmp/PartAdvancedModem.java b/remappedSrc/dan200/computercraft/shared/integration/mcmp/PartAdvancedModem.java new file mode 100644 index 000000000..5c6a7a438 --- /dev/null +++ b/remappedSrc/dan200/computercraft/shared/integration/mcmp/PartAdvancedModem.java @@ -0,0 +1,41 @@ +/* + * 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.shared.integration.mcmp; + +import dan200.computercraft.ComputerCraft; +import dan200.computercraft.shared.peripheral.modem.wireless.BlockAdvancedModem; +import mcmultipart.api.multipart.IMultipart; +import mcmultipart.api.slot.EnumFaceSlot; +import mcmultipart.api.slot.IPartSlot; +import net.minecraft.block.Block; +import net.minecraft.block.state.IBlockState; +import net.minecraft.entity.EntityLivingBase; +import net.minecraft.util.Direction; +import net.minecraft.util.math.BlockPos; +import net.minecraft.world.IBlockAccess; +import net.minecraft.world.World; + +public class PartAdvancedModem implements IMultipart +{ + @Override + public IPartSlot getSlotForPlacement( World world, BlockPos pos, IBlockState state, Direction facing, float hitX, float hitY, float hitZ, EntityLivingBase placer ) + { + return EnumFaceSlot.fromFace( state.getValue( BlockAdvancedModem.FACING ) ); + } + + @Override + public IPartSlot getSlotFromWorld( IBlockAccess world, BlockPos pos, IBlockState state ) + { + return EnumFaceSlot.fromFace( state.getValue( BlockAdvancedModem.FACING ) ); + } + + @Override + public Block getBlock() + { + return ComputerCraft.Blocks.advancedModem; + } +} diff --git a/remappedSrc/dan200/computercraft/shared/integration/mcmp/PartPeripheral.java b/remappedSrc/dan200/computercraft/shared/integration/mcmp/PartPeripheral.java new file mode 100644 index 000000000..587493830 --- /dev/null +++ b/remappedSrc/dan200/computercraft/shared/integration/mcmp/PartPeripheral.java @@ -0,0 +1,67 @@ +/* + * 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.shared.integration.mcmp; + +import dan200.computercraft.ComputerCraft; +import dan200.computercraft.shared.peripheral.common.BlockPeripheral; +import dan200.computercraft.shared.peripheral.common.BlockPeripheralVariant; +import mcmultipart.api.multipart.IMultipart; +import mcmultipart.api.slot.EnumCenterSlot; +import mcmultipart.api.slot.EnumFaceSlot; +import mcmultipart.api.slot.IPartSlot; +import net.minecraft.block.Block; +import net.minecraft.block.state.IBlockState; +import net.minecraft.entity.EntityLivingBase; +import net.minecraft.util.Direction; +import net.minecraft.util.math.BlockPos; +import net.minecraft.world.IBlockAccess; +import net.minecraft.world.World; + +import javax.annotation.Nonnull; + +public class PartPeripheral implements IMultipart +{ + @Override + public IPartSlot getSlotForPlacement( World world, BlockPos pos, IBlockState state, Direction facing, float hitX, float hitY, float hitZ, EntityLivingBase placer ) + { + return getSlot( state ); + } + + @Override + public IPartSlot getSlotFromWorld( IBlockAccess world, BlockPos pos, IBlockState state ) + { + return getSlot( state ); + } + + @Nonnull + private static IPartSlot getSlot( IBlockState state ) + { + BlockPeripheralVariant type = state.getValue( BlockPeripheral.VARIANT ); + if( type == BlockPeripheralVariant.WirelessModemUpOn || type == BlockPeripheralVariant.WirelessModemUpOff ) + { + return EnumFaceSlot.UP; + } + else if( type == BlockPeripheralVariant.WirelessModemDownOn || type == BlockPeripheralVariant.WirelessModemDownOff ) + { + return EnumFaceSlot.DOWN; + } + else if( type == BlockPeripheralVariant.WirelessModemOff || type == BlockPeripheralVariant.WirelessModemOn ) + { + return EnumFaceSlot.fromFace( state.getValue( BlockPeripheral.FACING ) ); + } + else + { + return EnumCenterSlot.CENTER; + } + } + + @Override + public Block getBlock() + { + return ComputerCraft.Blocks.peripheral; + } +} diff --git a/remappedSrc/dan200/computercraft/shared/media/items/ItemDisk.java b/remappedSrc/dan200/computercraft/shared/media/items/ItemDisk.java new file mode 100644 index 000000000..345d253d1 --- /dev/null +++ b/remappedSrc/dan200/computercraft/shared/media/items/ItemDisk.java @@ -0,0 +1,123 @@ +/* + * 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.shared.media.items; + +import dan200.computercraft.ComputerCraft; +import dan200.computercraft.api.ComputerCraftAPI; +import dan200.computercraft.api.filesystem.IMount; +import dan200.computercraft.api.media.IMedia; +import dan200.computercraft.shared.common.IColouredItem; +import dan200.computercraft.shared.util.Colour; +import net.minecraft.client.item.TooltipContext; +import net.minecraft.item.Item; +import net.minecraft.item.ItemGroup; +import net.minecraft.item.ItemStack; +import net.minecraft.nbt.CompoundTag; +import net.minecraft.text.LiteralText; +import net.minecraft.text.Text; +import net.minecraft.text.TranslatableText; +import net.minecraft.util.Formatting; +import net.minecraft.util.collection.DefaultedList; +import net.minecraft.world.World; + +import javax.annotation.Nonnull; +import javax.annotation.Nullable; +import java.util.List; + +public class ItemDisk extends Item implements IMedia, IColouredItem +{ + private static final String NBT_ID = "DiskId"; + + public ItemDisk( Settings settings ) + { + super( settings ); + } + + @Nonnull + public static ItemStack createFromIDAndColour( int id, String label, int colour ) + { + ItemStack stack = new ItemStack( ComputerCraft.Items.disk ); + setDiskID( stack, id ); + ComputerCraft.Items.disk.setLabel( stack, label ); + IColouredItem.setColourBasic( stack, colour ); + return stack; + } + + @Override + public void appendStacks( @Nonnull ItemGroup tabs, @Nonnull DefaultedList list ) + { + if( !isIn( tabs ) ) return; + for( int colour = 0; colour < 16; colour++ ) + { + list.add( createFromIDAndColour( -1, null, Colour.VALUES[colour].getHex() ) ); + } + } + + @Override + public void appendTooltip( ItemStack stack, @Nullable World world, List list, TooltipContext options ) + { + if( options.isAdvanced() ) + { + int id = getDiskID( stack ); + if( id >= 0 ) + { + list.add( new TranslatableText( "gui.computercraft.tooltip.disk_id", id ) + .formatted( Formatting.GRAY ) ); + } + } + } + + @Override + public String getLabel( @Nonnull ItemStack stack ) + { + return stack.hasCustomName() ? stack.getName().getString() : null; + } + + @Override + public boolean setLabel( @Nonnull ItemStack stack, String label ) + { + if( label != null ) + { + stack.setCustomName( new LiteralText( label ) ); + } + else + { + stack.removeCustomName(); + } + return true; + } + + @Override + public IMount createDataMount( @Nonnull ItemStack stack, @Nonnull World world ) + { + int diskID = getDiskID( stack ); + if( diskID < 0 ) + { + diskID = ComputerCraftAPI.createUniqueNumberedSaveDir( world, "disk" ); + setDiskID( stack, diskID ); + } + return ComputerCraftAPI.createSaveDirMount( world, "disk/" + diskID, ComputerCraft.floppySpaceLimit ); + } + + public static int getDiskID( @Nonnull ItemStack stack ) + { + CompoundTag nbt = stack.getTag(); + return nbt != null && nbt.contains( NBT_ID ) ? nbt.getInt( NBT_ID ) : -1; + } + + private static void setDiskID( @Nonnull ItemStack stack, int id ) + { + if( id >= 0 ) stack.getOrCreateTag().putInt( NBT_ID, id ); + } + + @Override + public int getColour( @Nonnull ItemStack stack ) + { + int colour = IColouredItem.getColourBasic( stack ); + return colour == -1 ? Colour.White.getHex() : colour; + } +} diff --git a/remappedSrc/dan200/computercraft/shared/media/items/ItemPrintout.java b/remappedSrc/dan200/computercraft/shared/media/items/ItemPrintout.java new file mode 100644 index 000000000..a3fb6c45e --- /dev/null +++ b/remappedSrc/dan200/computercraft/shared/media/items/ItemPrintout.java @@ -0,0 +1,151 @@ +/* + * 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.shared.media.items; + +import dan200.computercraft.ComputerCraft; +import dan200.computercraft.shared.network.Containers; +import net.minecraft.client.item.TooltipContext; +import net.minecraft.entity.player.PlayerEntity; +import net.minecraft.item.Item; +import net.minecraft.item.ItemStack; +import net.minecraft.nbt.CompoundTag; +import net.minecraft.text.LiteralText; +import net.minecraft.text.Text; +import net.minecraft.util.ActionResult; +import net.minecraft.util.Hand; +import net.minecraft.util.TypedActionResult; +import net.minecraft.world.World; + +import javax.annotation.Nonnull; +import java.util.List; + +public class ItemPrintout extends Item +{ + private static final String NBT_TITLE = "Title"; + private static final String NBT_PAGES = "Pages"; + private static final String NBT_LINE_TEXT = "Text"; + private static final String NBT_LINE_COLOUR = "Color"; + + public static final int LINES_PER_PAGE = 21; + public static final int LINE_MAX_LENGTH = 25; + public static final int MAX_PAGES = 16; + + public enum Type + { + PAGE, + PAGES, + BOOK + } + + private final Type type; + + public ItemPrintout( Settings settings, Type type ) + { + super( settings ); + this.type = type; + } + + @Override + public void appendTooltip( @Nonnull ItemStack stack, World world, List list, TooltipContext options ) + { + String title = getTitle( stack ); + if( title != null && !title.isEmpty() ) list.add( new LiteralText( title ) ); + } + + @Override + public TypedActionResult use( World world, PlayerEntity player, Hand hand ) + { + if( !world.isClient ) Containers.openPrintoutGUI( player, hand ); + return new TypedActionResult<>( ActionResult.SUCCESS, player.getStackInHand( hand ) ); + } + + @Nonnull + private ItemStack createFromTitleAndText( String title, String[] text, String[] colours ) + { + ItemStack stack = new ItemStack( this ); + + // Build NBT + if( title != null ) stack.getOrCreateTag().putString( NBT_TITLE, title ); + if( text != null ) + { + CompoundTag tag = stack.getOrCreateTag(); + tag.putInt( NBT_PAGES, text.length / LINES_PER_PAGE ); + for( int i = 0; i < text.length; i++ ) + { + if( text[i] != null ) tag.putString( NBT_LINE_TEXT + i, text[i] ); + } + } + if( colours != null ) + { + CompoundTag tag = stack.getOrCreateTag(); + for( int i = 0; i < colours.length; i++ ) + { + if( colours[i] != null ) tag.putString( NBT_LINE_COLOUR + i, colours[i] ); + } + } + + + return stack; + } + + @Nonnull + public static ItemStack createSingleFromTitleAndText( String title, String[] text, String[] colours ) + { + return ComputerCraft.Items.printedPage.createFromTitleAndText( title, text, colours ); + } + + @Nonnull + public static ItemStack createMultipleFromTitleAndText( String title, String[] text, String[] colours ) + { + return ComputerCraft.Items.printedPages.createFromTitleAndText( title, text, colours ); + } + + @Nonnull + public static ItemStack createBookFromTitleAndText( String title, String[] text, String[] colours ) + { + return ComputerCraft.Items.printedBook.createFromTitleAndText( title, text, colours ); + } + + public Type getType() + { + return type; + } + + public static String getTitle( @Nonnull ItemStack stack ) + { + CompoundTag nbt = stack.getTag(); + return nbt != null && nbt.contains( NBT_TITLE ) ? nbt.getString( NBT_TITLE ) : null; + } + + public static int getPageCount( @Nonnull ItemStack stack ) + { + CompoundTag nbt = stack.getTag(); + return nbt != null && nbt.contains( NBT_PAGES ) ? nbt.getInt( NBT_PAGES ) : 1; + } + + public static String[] getText( @Nonnull ItemStack stack ) + { + return getLines( stack, NBT_LINE_TEXT ); + } + + public static String[] getColours( @Nonnull ItemStack stack ) + { + return getLines( stack, NBT_LINE_COLOUR ); + } + + private static String[] getLines( @Nonnull ItemStack stack, String prefix ) + { + CompoundTag nbt = stack.getTag(); + int numLines = getPageCount( stack ) * LINES_PER_PAGE; + String[] lines = new String[numLines]; + for( int i = 0; i < lines.length; i++ ) + { + lines[i] = nbt != null ? nbt.getString( prefix + i ) : ""; + } + return lines; + } +} diff --git a/remappedSrc/dan200/computercraft/shared/media/items/ItemTreasureDisk.java b/remappedSrc/dan200/computercraft/shared/media/items/ItemTreasureDisk.java new file mode 100644 index 000000000..f90b34f12 --- /dev/null +++ b/remappedSrc/dan200/computercraft/shared/media/items/ItemTreasureDisk.java @@ -0,0 +1,130 @@ +/* + * 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.shared.media.items; + +import dan200.computercraft.ComputerCraft; +import dan200.computercraft.api.ComputerCraftAPI; +import dan200.computercraft.api.filesystem.IMount; +import dan200.computercraft.api.media.IMedia; +import dan200.computercraft.core.filesystem.SubMount; +import dan200.computercraft.shared.util.Colour; +import net.minecraft.client.item.TooltipContext; +import net.minecraft.item.Item; +import net.minecraft.item.ItemGroup; +import net.minecraft.item.ItemStack; +import net.minecraft.nbt.CompoundTag; +import net.minecraft.text.LiteralText; +import net.minecraft.text.Text; +import net.minecraft.util.collection.DefaultedList; +import net.minecraft.world.World; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; +import java.io.IOException; +import java.util.List; + +public class ItemTreasureDisk extends Item implements IMedia +{ + private static final String NBT_TITLE = "Title"; + private static final String NBT_COLOUR = "Colour"; + private static final String NBT_SUB_PATH = "SubPath"; + + public ItemTreasureDisk( Settings settings ) + { + super( settings ); + } + + @Override + public void appendStacks( @Nonnull ItemGroup group, @Nonnull DefaultedList stacks ) + { + } + + @Override + public void appendTooltip( ItemStack stack, @Nullable World world, List list, TooltipContext context ) + { + String label = getTitle( stack ); + if( !label.isEmpty() ) list.add( new LiteralText( label ) ); + } + + @Override + public String getLabel( @Nonnull ItemStack stack ) + { + return getTitle( stack ); + } + + @Override + public IMount createDataMount( @Nonnull ItemStack stack, @Nonnull World world ) + { + IMount rootTreasure = getTreasureMount(); + String subPath = getSubPath( stack ); + try + { + if( rootTreasure.exists( subPath ) ) + { + return new SubMount( rootTreasure, subPath ); + } + else if( rootTreasure.exists( "deprecated/" + subPath ) ) + { + return new SubMount( rootTreasure, "deprecated/" + subPath ); + } + else + { + return null; + } + } + catch( IOException e ) + { + return null; + } + } + + public static ItemStack create( String subPath, int colourIndex ) + { + ItemStack result = new ItemStack( ComputerCraft.Items.treasureDisk ); + CompoundTag nbt = result.getOrCreateTag(); + nbt.putString( NBT_SUB_PATH, subPath ); + + int slash = subPath.indexOf( '/' ); + if( slash >= 0 ) + { + String author = subPath.substring( 0, slash ); + String title = subPath.substring( slash + 1 ); + nbt.putString( NBT_TITLE, "\"" + title + "\" by " + author ); + } + else + { + nbt.putString( NBT_TITLE, "untitled" ); + } + nbt.putInt( NBT_COLOUR, Colour.values()[colourIndex].getHex() ); + + return result; + } + + private static IMount getTreasureMount() + { + return ComputerCraftAPI.createResourceMount( "computercraft", "lua/treasure" ); + } + + @Nonnull + private static String getTitle( @Nonnull ItemStack stack ) + { + CompoundTag nbt = stack.getTag(); + return nbt != null && nbt.contains( NBT_TITLE ) ? nbt.getString( NBT_TITLE ) : "'alongtimeago' by dan200"; + } + + @Nonnull + private static String getSubPath( @Nonnull ItemStack stack ) + { + CompoundTag nbt = stack.getTag(); + return nbt != null && nbt.contains( NBT_SUB_PATH ) ? nbt.getString( NBT_SUB_PATH ) : "dan200/alongtimeago"; + } + + public static int getColour( @Nonnull ItemStack stack ) + { + CompoundTag nbt = stack.getTag(); + return nbt != null && nbt.contains( NBT_COLOUR ) ? nbt.getInt( NBT_COLOUR ) : Colour.Blue.getHex(); + } +} diff --git a/remappedSrc/dan200/computercraft/shared/media/items/RecordMedia.java b/remappedSrc/dan200/computercraft/shared/media/items/RecordMedia.java new file mode 100644 index 000000000..17f139ea3 --- /dev/null +++ b/remappedSrc/dan200/computercraft/shared/media/items/RecordMedia.java @@ -0,0 +1,45 @@ +/* + * 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.shared.media.items; + +import dan200.computercraft.api.media.IMedia; +import dan200.computercraft.shared.util.RecordUtil; +import net.minecraft.item.ItemStack; +import net.minecraft.item.MusicDiscItem; +import net.minecraft.sound.SoundEvent; + +import javax.annotation.Nonnull; + +/** + * An implementation of IMedia for ItemRecord's + */ +public final class RecordMedia implements IMedia +{ + public static final RecordMedia INSTANCE = new RecordMedia(); + + private RecordMedia() + { + } + + @Override + public String getLabel( @Nonnull ItemStack stack ) + { + return getAudioTitle( stack ); + } + + @Override + public String getAudioTitle( @Nonnull ItemStack stack ) + { + return RecordUtil.getRecordInfo( stack ); + } + + @Override + public SoundEvent getAudio( @Nonnull ItemStack stack ) + { + return ((MusicDiscItem) stack.getItem()).getSound(); + } +} diff --git a/remappedSrc/dan200/computercraft/shared/media/recipes/DiskRecipe.java b/remappedSrc/dan200/computercraft/shared/media/recipes/DiskRecipe.java new file mode 100644 index 000000000..24b9053e0 --- /dev/null +++ b/remappedSrc/dan200/computercraft/shared/media/recipes/DiskRecipe.java @@ -0,0 +1,114 @@ +/* + * 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.shared.media.recipes; + +import dan200.computercraft.shared.media.items.ItemDisk; +import dan200.computercraft.shared.util.Colour; +import dan200.computercraft.shared.util.ColourTracker; +import dan200.computercraft.shared.util.ColourUtils; +import net.minecraft.inventory.CraftingInventory; +import net.minecraft.item.ItemStack; +import net.minecraft.item.Items; +import net.minecraft.recipe.Ingredient; +import net.minecraft.recipe.RecipeSerializer; +import net.minecraft.recipe.SpecialCraftingRecipe; +import net.minecraft.recipe.SpecialRecipeSerializer; +import net.minecraft.util.DyeColor; +import net.minecraft.util.Identifier; +import net.minecraft.world.World; + +import javax.annotation.Nonnull; + +public class DiskRecipe extends SpecialCraftingRecipe +{ + private final Ingredient paper = Ingredient.ofItems( Items.PAPER ); + private final Ingredient redstone = Ingredient.ofItems( Items.REDSTONE ); + + public DiskRecipe( Identifier id ) + { + super( id ); + } + + @Override + public boolean matches( @Nonnull CraftingInventory inv, @Nonnull World world ) + { + boolean paperFound = false; + boolean redstoneFound = false; + + for( int i = 0; i < inv.size(); i++ ) + { + ItemStack stack = inv.getStack( i ); + + if( !stack.isEmpty() ) + { + if( paper.test( stack ) ) + { + if( paperFound ) return false; + paperFound = true; + } + else if( redstone.test( stack ) ) + { + if( redstoneFound ) return false; + redstoneFound = true; + } + else if( ColourUtils.getStackColour( stack ) != null ) + { + return false; + } + } + } + + return redstoneFound && paperFound; + } + + @Nonnull + @Override + public ItemStack craft( @Nonnull CraftingInventory inv ) + { + ColourTracker tracker = new ColourTracker(); + + for( int i = 0; i < inv.size(); i++ ) + { + ItemStack stack = inv.getStack( i ); + + if( stack.isEmpty() ) continue; + + if( !paper.test( stack ) && !redstone.test( stack ) ) + { + DyeColor dye = ColourUtils.getStackColour( stack ); + if( dye == null ) continue; + + Colour colour = Colour.VALUES[dye.getId()]; + tracker.addColour( colour.getR(), colour.getG(), colour.getB() ); + } + } + + return ItemDisk.createFromIDAndColour( -1, null, tracker.hasColour() ? tracker.getColour() : Colour.Blue.getHex() ); + } + + @Override + public boolean fits( int x, int y ) + { + return x >= 2 && y >= 2; + } + + @Nonnull + @Override + public ItemStack getOutput() + { + return ItemDisk.createFromIDAndColour( -1, null, Colour.Blue.getHex() ); + } + + @Nonnull + @Override + public RecipeSerializer getSerializer() + { + return SERIALIZER; + } + + public static final RecipeSerializer SERIALIZER = new SpecialRecipeSerializer<>( DiskRecipe::new ); +} diff --git a/remappedSrc/dan200/computercraft/shared/media/recipes/PrintoutRecipe.java b/remappedSrc/dan200/computercraft/shared/media/recipes/PrintoutRecipe.java new file mode 100644 index 000000000..56b5cc0a4 --- /dev/null +++ b/remappedSrc/dan200/computercraft/shared/media/recipes/PrintoutRecipe.java @@ -0,0 +1,168 @@ +/* + * 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.shared.media.recipes; + +import dan200.computercraft.shared.media.items.ItemPrintout; +import net.minecraft.inventory.CraftingInventory; +import net.minecraft.item.ItemStack; +import net.minecraft.item.Items; +import net.minecraft.recipe.Ingredient; +import net.minecraft.recipe.RecipeSerializer; +import net.minecraft.recipe.SpecialCraftingRecipe; +import net.minecraft.recipe.SpecialRecipeSerializer; +import net.minecraft.util.Identifier; +import net.minecraft.world.World; + +import javax.annotation.Nonnull; + +public final class PrintoutRecipe extends SpecialCraftingRecipe +{ + private final Ingredient paper = Ingredient.ofItems( Items.PAPER ); + private final Ingredient leather = Ingredient.ofItems( Items.LEATHER ); + private final Ingredient string = Ingredient.ofItems( Items.STRING ); + + public PrintoutRecipe( Identifier id ) + { + super( id ); + } + + @Override + public boolean fits( int x, int y ) + { + return x >= 3 && y >= 3; + } + + @Nonnull + @Override + public ItemStack getOutput() + { + return ItemPrintout.createMultipleFromTitleAndText( null, null, null ); + } + + @Override + public boolean matches( @Nonnull CraftingInventory inventory, @Nonnull World world ) + { + return !craft( inventory ).isEmpty(); + } + + @Nonnull + @Override + public ItemStack craft( @Nonnull CraftingInventory inventory ) + { + // See if we match the recipe, and extract the input disk ID and dye colour + int numPages = 0; + int numPrintouts = 0; + ItemStack[] printouts = null; + boolean stringFound = false; + boolean leatherFound = false; + boolean printoutFound = false; + for( int y = 0; y < inventory.getHeight(); y++ ) + { + for( int x = 0; x < inventory.getWidth(); x++ ) + { + ItemStack stack = inventory.getStack( x + y * inventory.getWidth() ); + if( !stack.isEmpty() ) + { + if( stack.getItem() instanceof ItemPrintout && ((ItemPrintout) stack.getItem()).getType() != ItemPrintout.Type.BOOK ) + { + if( printouts == null ) + { + printouts = new ItemStack[9]; + } + printouts[numPrintouts] = stack; + numPages += ItemPrintout.getPageCount( stack ); + numPrintouts++; + printoutFound = true; + } + else if( paper.test( stack ) ) + { + if( printouts == null ) + { + printouts = new ItemStack[9]; + } + printouts[numPrintouts] = stack; + numPages++; + numPrintouts++; + } + else if( string.test( stack ) && !stringFound ) + { + stringFound = true; + } + else if( leather.test( stack ) && !leatherFound ) + { + leatherFound = true; + } + else + { + return ItemStack.EMPTY; + } + } + } + } + + // Build some pages with what was passed in + if( numPages <= ItemPrintout.MAX_PAGES && stringFound && printoutFound && numPrintouts >= (leatherFound ? 1 : 2) ) + { + String[] text = new String[numPages * ItemPrintout.LINES_PER_PAGE]; + String[] colours = new String[numPages * ItemPrintout.LINES_PER_PAGE]; + int line = 0; + + for( int printout = 0; printout < numPrintouts; printout++ ) + { + ItemStack stack = printouts[printout]; + if( stack.getItem() instanceof ItemPrintout ) + { + // Add a printout + String[] pageText = ItemPrintout.getText( printouts[printout] ); + String[] pageColours = ItemPrintout.getColours( printouts[printout] ); + for( int pageLine = 0; pageLine < pageText.length; pageLine++ ) + { + text[line] = pageText[pageLine]; + colours[line] = pageColours[pageLine]; + line++; + } + } + else + { + // Add a blank page + for( int pageLine = 0; pageLine < ItemPrintout.LINES_PER_PAGE; pageLine++ ) + { + text[line] = ""; + colours[line] = ""; + line++; + } + } + } + + String title = null; + if( printouts[0].getItem() instanceof ItemPrintout ) + { + title = ItemPrintout.getTitle( printouts[0] ); + } + + if( leatherFound ) + { + return ItemPrintout.createBookFromTitleAndText( title, text, colours ); + } + else + { + return ItemPrintout.createMultipleFromTitleAndText( title, text, colours ); + } + } + + return ItemStack.EMPTY; + } + + @Nonnull + @Override + public RecipeSerializer getSerializer() + { + return SERIALIZER; + } + + public static final RecipeSerializer SERIALIZER = new SpecialRecipeSerializer<>( PrintoutRecipe::new ); +} diff --git a/remappedSrc/dan200/computercraft/shared/mixed/MixedFirstPersonRenderer.java b/remappedSrc/dan200/computercraft/shared/mixed/MixedFirstPersonRenderer.java new file mode 100644 index 000000000..464818962 --- /dev/null +++ b/remappedSrc/dan200/computercraft/shared/mixed/MixedFirstPersonRenderer.java @@ -0,0 +1,18 @@ +/* + * 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.shared.mixed; + +import net.minecraft.util.Arm; + +public interface MixedFirstPersonRenderer +{ + void renderArms_CC(); + + void renderArmFirstPerson_CC( float equip, float swing, Arm hand ); + + float getMapAngleFromPitch_CC( float pitch ); +} diff --git a/remappedSrc/dan200/computercraft/shared/mixin/MixinBlock.java b/remappedSrc/dan200/computercraft/shared/mixin/MixinBlock.java new file mode 100644 index 000000000..47846b59c --- /dev/null +++ b/remappedSrc/dan200/computercraft/shared/mixin/MixinBlock.java @@ -0,0 +1,34 @@ +/* + * 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.shared.mixin; + +import dan200.computercraft.shared.util.DropConsumer; +import net.minecraft.block.Block; +import net.minecraft.item.ItemStack; +import net.minecraft.util.math.BlockPos; +import net.minecraft.world.World; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; + +/** + * @see Block#dropStack(World, BlockPos, ItemStack) + */ +@Mixin( Block.class ) +public class MixinBlock +{ + @Inject( + method = "dropStack", + at = @At( value = "INVOKE", target = "Lnet/minecraft/world/World;spawnEntity(Lnet/minecraft/entity/Entity;)Z" ), + cancellable = true + ) + private static void dropStack( World world, BlockPos pos, ItemStack stack, CallbackInfo callbackInfo ) + { + if( DropConsumer.onHarvestDrops( world, pos, stack ) ) callbackInfo.cancel(); + } +} diff --git a/remappedSrc/dan200/computercraft/shared/mixin/MixinEntity.java b/remappedSrc/dan200/computercraft/shared/mixin/MixinEntity.java new file mode 100644 index 000000000..7403d7216 --- /dev/null +++ b/remappedSrc/dan200/computercraft/shared/mixin/MixinEntity.java @@ -0,0 +1,33 @@ +/* + * 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.shared.mixin; + +import dan200.computercraft.shared.util.DropConsumer; +import net.minecraft.entity.Entity; +import net.minecraft.entity.ItemEntity; +import net.minecraft.item.ItemStack; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; + +/** + * @see Entity#dropStack(ItemStack, float) + */ +@Mixin( Entity.class ) +public class MixinEntity +{ + @Inject( + method = "dropStack(Lnet/minecraft/item/ItemStack;F)Lnet/minecraft/entity/ItemEntity;", + at = @At( value = "INVOKE", target = "Lnet/minecraft/world/World;spawnEntity(Lnet/minecraft/entity/Entity;)Z" ), + cancellable = true + ) + public void dropStack( ItemStack stack, float height, CallbackInfoReturnable callbackInfo ) + { + if( DropConsumer.onEntityLivingDrops( (Entity) (Object) this, stack ) ) callbackInfo.setReturnValue( null ); + } +} diff --git a/remappedSrc/dan200/computercraft/shared/mixin/MixinFirstPersonRenderer.java b/remappedSrc/dan200/computercraft/shared/mixin/MixinFirstPersonRenderer.java new file mode 100644 index 000000000..8efd7f4c9 --- /dev/null +++ b/remappedSrc/dan200/computercraft/shared/mixin/MixinFirstPersonRenderer.java @@ -0,0 +1,80 @@ +/* + * 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.shared.mixin; + +import dan200.computercraft.client.render.ItemPocketRenderer; +import dan200.computercraft.client.render.ItemPrintoutRenderer; +import dan200.computercraft.shared.media.items.ItemPrintout; +import dan200.computercraft.shared.mixed.MixedFirstPersonRenderer; +import dan200.computercraft.shared.pocket.items.ItemPocketComputer; +import net.minecraft.client.network.AbstractClientPlayerEntity; +import net.minecraft.client.render.item.HeldItemRenderer; +import net.minecraft.item.ItemStack; +import net.minecraft.util.Arm; +import net.minecraft.util.Hand; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Shadow; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; + +@Mixin( HeldItemRenderer.class ) +public class MixinFirstPersonRenderer implements MixedFirstPersonRenderer +{ + @Shadow + private float getMapAngle( float pitch ) + { + return 0; + } + + @Shadow + private void renderArms() + { + } + + @Shadow + private void renderArmHoldingItem( float equip, float swing, Arm hand ) + { + } + + @Override + public void renderArms_CC() + { + renderArms(); + } + + @Override + public void renderArmFirstPerson_CC( float equip, float swing, Arm hand ) + { + renderArmHoldingItem( equip, swing, hand ); + } + + @Override + public float getMapAngleFromPitch_CC( float pitch ) + { + return getMapAngle( pitch ); + } + + @Inject( + method = "renderFirstPersonItem(Lnet/minecraft/client/network/AbstractClientPlayerEntity;FFLnet/minecraft/util/Hand;FLnet/minecraft/item/ItemStack;F)V", + at = @At( "HEAD" ), + cancellable = true + ) + public void renderFirstPersonItem_Injected( AbstractClientPlayerEntity player, float var2, float pitch, Hand hand, float swingProgress, ItemStack stack, float equipProgress, CallbackInfo callback ) + { + if( stack.getItem() instanceof ItemPrintout ) + { + ItemPrintoutRenderer.INSTANCE.renderItemFirstPerson( hand, pitch, equipProgress, swingProgress, stack ); + callback.cancel(); + } + else if( stack.getItem() instanceof ItemPocketComputer ) + { + ItemPocketRenderer.INSTANCE.renderItemFirstPerson( hand, pitch, equipProgress, swingProgress, stack ); + callback.cancel(); + } + } +} diff --git a/remappedSrc/dan200/computercraft/shared/mixin/MixinItemFrameEntityRenderer.java b/remappedSrc/dan200/computercraft/shared/mixin/MixinItemFrameEntityRenderer.java new file mode 100644 index 000000000..8a9ca27f1 --- /dev/null +++ b/remappedSrc/dan200/computercraft/shared/mixin/MixinItemFrameEntityRenderer.java @@ -0,0 +1,32 @@ +/* + * 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.shared.mixin; + +import dan200.computercraft.client.render.ItemPrintoutRenderer; +import dan200.computercraft.shared.media.items.ItemPrintout; +import net.minecraft.client.render.entity.ItemFrameEntityRenderer; +import net.minecraft.entity.decoration.ItemFrameEntity; +import net.minecraft.item.ItemStack; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; + +@Mixin( ItemFrameEntityRenderer.class ) +public class MixinItemFrameEntityRenderer +{ + @Inject( method = "renderItem", at = @At( "HEAD" ), cancellable = true ) + private void renderItem_Injected( ItemFrameEntity entity, CallbackInfo info ) + { + ItemStack stack = entity.getHeldItemStack(); + if( stack.getItem() instanceof ItemPrintout ) + { + ItemPrintoutRenderer.INSTANCE.renderInFrame( entity, stack ); + info.cancel(); + } + } +} diff --git a/remappedSrc/dan200/computercraft/shared/mixin/MixinMinecraftGame.java b/remappedSrc/dan200/computercraft/shared/mixin/MixinMinecraftGame.java new file mode 100644 index 000000000..c786a5b32 --- /dev/null +++ b/remappedSrc/dan200/computercraft/shared/mixin/MixinMinecraftGame.java @@ -0,0 +1,27 @@ +/* + * 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.shared.mixin; + +import dan200.computercraft.client.FrameInfo; +import net.minecraft.client.MinecraftClient; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; + +@Mixin( MinecraftClient.class ) +public abstract class MixinMinecraftGame +{ + /** + * @see MinecraftClient#render(boolean) + */ + @Inject( method = "render", at = @At( "HEAD" ) ) + private void onRender( CallbackInfo info ) + { + FrameInfo.onRenderFrame(); + } +} diff --git a/remappedSrc/dan200/computercraft/shared/mixin/MixinScreen.java b/remappedSrc/dan200/computercraft/shared/mixin/MixinScreen.java new file mode 100644 index 000000000..e833a365d --- /dev/null +++ b/remappedSrc/dan200/computercraft/shared/mixin/MixinScreen.java @@ -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.shared.mixin; + +import dan200.computercraft.shared.command.CommandCopy; +import net.minecraft.client.gui.screen.Screen; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; + +@Mixin( Screen.class ) +public class MixinScreen +{ + @Inject( method = "sendMessage(Ljava/lang/String;Z)V", at = @At( "HEAD" ), cancellable = true ) + public void sendClientCommand( String message, boolean add, CallbackInfo info ) + { + if( CommandCopy.onClientSendMessage( message ) ) info.cancel(); + } +} diff --git a/remappedSrc/dan200/computercraft/shared/mixin/MixinServerWorld.java b/remappedSrc/dan200/computercraft/shared/mixin/MixinServerWorld.java new file mode 100644 index 000000000..97195a9f3 --- /dev/null +++ b/remappedSrc/dan200/computercraft/shared/mixin/MixinServerWorld.java @@ -0,0 +1,28 @@ +/* + * 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.shared.mixin; + +import dan200.computercraft.shared.util.DropConsumer; +import net.minecraft.entity.Entity; +import net.minecraft.server.world.ServerWorld; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; + +/** + * @see ServerWorld#spawnEntity(Entity) + */ +@Mixin( ServerWorld.class ) +public class MixinServerWorld +{ + @Inject( method = "spawnEntity", at = @At( "HEAD" ), cancellable = true ) + public void spawnEntity( Entity entity, CallbackInfoReturnable callbackInfo ) + { + if( DropConsumer.onEntitySpawn( entity ) ) callbackInfo.setReturnValue( false ); + } +} diff --git a/remappedSrc/dan200/computercraft/shared/mixin/MixinWorld.java b/remappedSrc/dan200/computercraft/shared/mixin/MixinWorld.java new file mode 100644 index 000000000..308488c0b --- /dev/null +++ b/remappedSrc/dan200/computercraft/shared/mixin/MixinWorld.java @@ -0,0 +1,55 @@ +/* + * 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.shared.mixin; + +import dan200.computercraft.shared.common.TileGeneric; +import net.minecraft.block.entity.BlockEntity; +import net.minecraft.util.math.BlockPos; +import net.minecraft.world.World; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Shadow; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; + +import javax.annotation.Nullable; +import java.util.Collection; + +/** + * Horrible bodge to ensure a {@link BlockEntity}'s world is always present when setting a TE during another TE's tick. + * + * Forge does this, this is just a bodge to get Fabric in line with that behaviour. + */ +@Mixin( World.class ) +public class MixinWorld +{ + @Shadow + protected boolean iteratingTickingBlockEntities; + + @Inject( method = "setBlockEntity", at = @At( "HEAD" ) ) + public void setBlockEntity( BlockPos pos, @Nullable BlockEntity entity, CallbackInfo info ) + { + if( !World.isHeightInvalid( pos ) && entity != null && !entity.isRemoved() && iteratingTickingBlockEntities ) + { + setWorld( entity, this ); + } + } + + @Inject( method = "addBlockEntities", at = @At( "HEAD" ) ) + public void addBlockEntities( Collection entities, CallbackInfo info ) + { + if( iteratingTickingBlockEntities ) + { + for( BlockEntity entity : entities ) setWorld( entity, this ); + } + } + + private static void setWorld( BlockEntity entity, Object world ) + { + if( entity.getWorld() != world && entity instanceof TileGeneric ) entity.setLocation( (World) world ); + } +} diff --git a/remappedSrc/dan200/computercraft/shared/mixin/MixinWorldRenderer.java b/remappedSrc/dan200/computercraft/shared/mixin/MixinWorldRenderer.java new file mode 100644 index 000000000..696bf648d --- /dev/null +++ b/remappedSrc/dan200/computercraft/shared/mixin/MixinWorldRenderer.java @@ -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.shared.mixin; + +import dan200.computercraft.client.render.CableHighlightRenderer; +import dan200.computercraft.client.render.MonitorHighlightRenderer; +import net.minecraft.client.render.Camera; +import net.minecraft.client.render.WorldRenderer; +import net.minecraft.util.hit.BlockHitResult; +import net.minecraft.util.hit.HitResult; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; + +@Mixin( WorldRenderer.class ) +public class MixinWorldRenderer +{ + @Inject( method = "drawHighlightedBlockOutline", cancellable = true, at = @At( "HEAD" ) ) + public void drawHighlightedBlockOutline( Camera camera, HitResult hit, int flag, CallbackInfo info ) + { + if( flag != 0 || hit.getType() != HitResult.Type.BLOCK ) return; + + BlockHitResult blockHit = (BlockHitResult) hit; + if( CableHighlightRenderer.drawHighlight( camera, blockHit ) || + MonitorHighlightRenderer.drawHighlight( camera, blockHit ) ) + { + info.cancel(); + } + } +} diff --git a/remappedSrc/dan200/computercraft/shared/network/Containers.java b/remappedSrc/dan200/computercraft/shared/network/Containers.java new file mode 100644 index 000000000..e08565055 --- /dev/null +++ b/remappedSrc/dan200/computercraft/shared/network/Containers.java @@ -0,0 +1,102 @@ +/* + * 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.shared.network; + +import dan200.computercraft.ComputerCraft; +import dan200.computercraft.shared.common.ContainerHeldItem; +import dan200.computercraft.shared.computer.blocks.TileComputer; +import dan200.computercraft.shared.computer.core.ServerComputer; +import dan200.computercraft.shared.computer.inventory.ContainerComputer; +import dan200.computercraft.shared.computer.inventory.ContainerViewComputer; +import dan200.computercraft.shared.media.items.ItemPrintout; +import dan200.computercraft.shared.network.container.*; +import dan200.computercraft.shared.peripheral.diskdrive.ContainerDiskDrive; +import dan200.computercraft.shared.peripheral.diskdrive.TileDiskDrive; +import dan200.computercraft.shared.peripheral.printer.ContainerPrinter; +import dan200.computercraft.shared.peripheral.printer.TilePrinter; +import dan200.computercraft.shared.pocket.inventory.ContainerPocketComputer; +import dan200.computercraft.shared.pocket.items.ItemPocketComputer; +import dan200.computercraft.shared.turtle.blocks.TileTurtle; +import dan200.computercraft.shared.turtle.inventory.ContainerTurtle; +import net.minecraft.entity.player.PlayerEntity; +import net.minecraft.item.Item; +import net.minecraft.item.ItemStack; +import net.minecraft.util.Hand; + +public final class Containers +{ + private Containers() + { + } + + public static void openDiskDriveGUI( PlayerEntity player, TileDiskDrive drive ) + { + TileEntityContainerType.diskDrive( drive.getPos() ).open( player ); + } + + public static void openComputerGUI( PlayerEntity player, TileComputer computer ) + { + computer.createServerComputer().sendTerminalState( player ); + TileEntityContainerType.computer( computer.getPos() ).open( player ); + } + + public static void openPrinterGUI( PlayerEntity player, TilePrinter printer ) + { + TileEntityContainerType.printer( printer.getPos() ).open( player ); + } + + public static void openTurtleGUI( PlayerEntity player, TileTurtle turtle ) + { + turtle.createServerComputer().sendTerminalState( player ); + TileEntityContainerType.turtle( turtle.getPos() ).open( player ); + } + + public static void openPrintoutGUI( PlayerEntity player, Hand hand ) + { + ItemStack stack = player.getStackInHand( hand ); + Item item = stack.getItem(); + if( !(item instanceof ItemPrintout) ) return; + + new PrintoutContainerType( hand ).open( player ); + } + + public static void openPocketComputerGUI( PlayerEntity player, Hand hand ) + { + ItemStack stack = player.getStackInHand( hand ); + Item item = stack.getItem(); + if( !(item instanceof ItemPocketComputer) ) return; + + ServerComputer computer = ItemPocketComputer.getServerComputer( stack ); + if( computer != null ) computer.sendTerminalState( player ); + + new PocketComputerContainerType( hand ).open( player ); + } + + public static void openComputerGUI( PlayerEntity player, ServerComputer computer ) + { + computer.sendTerminalState( player ); + new ViewComputerContainerType( computer ).open( player ); + } + + public static void setup() + { + ContainerType.register( TileEntityContainerType::computer, ( id, packet, player ) -> + new ContainerComputer( id, (TileComputer) packet.getTileEntity( player ) ) ); + ContainerType.register( TileEntityContainerType::turtle, ( id, packet, player ) -> { + TileTurtle turtle = (TileTurtle) packet.getTileEntity( player ); + return new ContainerTurtle( id, player.inventory, turtle.getAccess(), turtle.getServerComputer() ); + } ); + ContainerType.register( TileEntityContainerType::diskDrive, ( id, packet, player ) -> + new ContainerDiskDrive( id, player.inventory, (TileDiskDrive) packet.getTileEntity( player ) ) ); + ContainerType.register( TileEntityContainerType::printer, ( id, packet, player ) -> + new ContainerPrinter( id, player.inventory, (TilePrinter) packet.getTileEntity( player ) ) ); + + ContainerType.register( PocketComputerContainerType::new, ( id, packet, player ) -> new ContainerPocketComputer( id, player, packet.hand ) ); + ContainerType.register( PrintoutContainerType::new, ( id, packet, player ) -> new ContainerHeldItem( id, player, packet.hand ) ); + ContainerType.register( ViewComputerContainerType::new, ( id, packet, player ) -> new ContainerViewComputer( id, ComputerCraft.serverComputerRegistry.get( packet.instanceId ) ) ); + } +} diff --git a/remappedSrc/dan200/computercraft/shared/network/NetworkHandler.java b/remappedSrc/dan200/computercraft/shared/network/NetworkHandler.java new file mode 100644 index 000000000..d60dd8d67 --- /dev/null +++ b/remappedSrc/dan200/computercraft/shared/network/NetworkHandler.java @@ -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.shared.network; + +import dan200.computercraft.ComputerCraft; +import dan200.computercraft.shared.network.client.*; +import dan200.computercraft.shared.network.server.*; +import io.netty.buffer.Unpooled; +import it.unimi.dsi.fastutil.ints.Int2ObjectMap; +import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap; +import it.unimi.dsi.fastutil.objects.Object2IntMap; +import it.unimi.dsi.fastutil.objects.Object2IntOpenHashMap; +import net.fabricmc.api.EnvType; +import net.fabricmc.fabric.api.network.ClientSidePacketRegistry; +import net.fabricmc.fabric.api.network.PacketContext; +import net.fabricmc.fabric.api.network.ServerSidePacketRegistry; +import net.fabricmc.loader.api.FabricLoader; +import net.minecraft.client.MinecraftClient; +import net.minecraft.entity.player.PlayerEntity; +import net.minecraft.network.PacketByteBuf; +import net.minecraft.network.packet.c2s.play.CustomPayloadC2SPacket; +import net.minecraft.network.packet.s2c.play.CustomPayloadS2CPacket; +import net.minecraft.server.MinecraftServer; +import net.minecraft.server.network.ServerPlayerEntity; +import net.minecraft.util.Identifier; +import net.minecraft.util.math.Vec3d; +import net.minecraft.world.World; + +import java.util.function.BiConsumer; +import java.util.function.Function; +import java.util.function.Supplier; + +public final class NetworkHandler +{ + private static final Int2ObjectMap> packetReaders = new Int2ObjectOpenHashMap<>(); + private static final Object2IntMap> packetIds = new Object2IntOpenHashMap<>(); + + private static final Identifier ID = new Identifier( ComputerCraft.MOD_ID, "main" ); + + private NetworkHandler() + { + } + + public static void setup() + { + ServerSidePacketRegistry.INSTANCE.register( ID, NetworkHandler::receive ); + if( FabricLoader.getInstance().getEnvironmentType() == EnvType.CLIENT ) + { + ClientSidePacketRegistry.INSTANCE.register( ID, NetworkHandler::receive ); + } + + // Server messages + registerMainThread( 0, ComputerActionServerMessage::new ); + registerMainThread( 1, QueueEventServerMessage::new ); + registerMainThread( 2, RequestComputerMessage::new ); + registerMainThread( 3, KeyEventServerMessage::new ); + registerMainThread( 4, MouseEventServerMessage::new ); + + // Client messages + registerMainThread( 10, ChatTableClientMessage::new ); + registerMainThread( 11, ComputerDataClientMessage::new ); + registerMainThread( 12, ComputerDeletedClientMessage::new ); + registerMainThread( 13, ComputerTerminalClientMessage::new ); + registerMainThread( 14, PlayRecordClientMessage.class, PlayRecordClientMessage::new ); + } + + public static void sendToPlayer( PlayerEntity player, NetworkMessage packet ) + { + ((ServerPlayerEntity) player).networkHandler.sendPacket( + new CustomPayloadS2CPacket( ID, encode( packet ) ) + ); + } + + public static void sendToAllPlayers( MinecraftServer server, NetworkMessage packet ) + { + server.getPlayerManager().sendToAll( new CustomPayloadS2CPacket( ID, encode( packet ) ) ); + } + + public static void sendToServer( NetworkMessage packet ) + { + MinecraftClient.getInstance().player.networkHandler.sendPacket( + new CustomPayloadC2SPacket( ID, encode( packet ) ) + ); + } + + public static void sendToAllAround( NetworkMessage packet, World world, Vec3d pos, double range ) + { + world.getServer().getPlayerManager().sendToAround( + null, pos.x, pos.y, pos.z, range, world.getDimension().getType(), + new CustomPayloadS2CPacket( ID, encode( packet ) ) + ); + } + + private static void receive( PacketContext context, PacketByteBuf buffer ) + { + int type = buffer.readByte(); + packetReaders.get( type ).accept( context, buffer ); + } + + private static PacketByteBuf encode( NetworkMessage message ) + { + PacketByteBuf buf = new PacketByteBuf( Unpooled.buffer() ); + buf.writeByte( packetIds.getInt( message.getClass() ) ); + message.toBytes( buf ); + return buf; + } + + /** + * /** + * Register packet, and a thread-unsafe handler for it. + * + * @param id The identifier for this packet type + * @param factory The factory for this type of packet. + */ + private static void registerMainThread( int id, Supplier factory ) + { + registerMainThread( id, getType( factory ), buf -> { + T instance = factory.get(); + instance.fromBytes( buf ); + return instance; + } ); + } + + /** + * /** + * Register packet, and a thread-unsafe handler for it. + * + * @param id The identifier for this packet type + * @param decoder The factory for this type of packet. + */ + private static void registerMainThread( int id, Class type, Function decoder ) + { + packetIds.put( type, id ); + packetReaders.put( id, ( context, buf ) -> { + T result = decoder.apply( buf ); + context.getTaskQueue().execute( () -> result.handle( context ) ); + } ); + } + + @SuppressWarnings( "unchecked" ) + private static Class getType( Supplier supplier ) + { + return (Class) supplier.get().getClass(); + } +} diff --git a/remappedSrc/dan200/computercraft/shared/network/NetworkMessage.java b/remappedSrc/dan200/computercraft/shared/network/NetworkMessage.java new file mode 100644 index 000000000..ed77457d6 --- /dev/null +++ b/remappedSrc/dan200/computercraft/shared/network/NetworkMessage.java @@ -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.shared.network; + +import net.fabricmc.fabric.api.network.PacketContext; +import net.minecraft.network.PacketByteBuf; +import javax.annotation.Nonnull; + +/** + * The base interface for any message which will be sent to the client or server. + * + * @see dan200.computercraft.shared.network.client + * @see dan200.computercraft.shared.network.server + * @see net.fabricmc.fabric.api.network.PacketRegistry + */ +public interface NetworkMessage +{ + /** + * Write this packet to a buffer. + * + * This may be called on any thread, so this should be a pure operation. + * + * @param buf The buffer to write data to. + */ + void toBytes( @Nonnull PacketByteBuf buf ); + + /** + * Read this packet from a buffer. + * + * This may be called on any thread, so this should be a pure operation. + * + * @param buf The buffer to read data from. + */ + default void fromBytes( @Nonnull PacketByteBuf buf ) + { + throw new IllegalStateException( "Should have been registered using a \"from bytes\" method" ); + } + + /** + * Handle this {@link NetworkMessage}. + * + * @param context The context with which to handle this message + */ + void handle( PacketContext context ); +} diff --git a/remappedSrc/dan200/computercraft/shared/network/client/ChatTableClientMessage.java b/remappedSrc/dan200/computercraft/shared/network/client/ChatTableClientMessage.java new file mode 100644 index 000000000..42f786872 --- /dev/null +++ b/remappedSrc/dan200/computercraft/shared/network/client/ChatTableClientMessage.java @@ -0,0 +1,88 @@ +/* + * 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.shared.network.client; + +import dan200.computercraft.client.ClientTableFormatter; +import dan200.computercraft.shared.command.text.TableBuilder; +import dan200.computercraft.shared.network.NetworkMessage; +import net.fabricmc.api.EnvType; +import net.fabricmc.api.Environment; +import net.fabricmc.fabric.api.network.PacketContext; +import net.minecraft.network.PacketByteBuf; +import net.minecraft.text.Text; +import javax.annotation.Nonnull; + +public class ChatTableClientMessage implements NetworkMessage +{ + private TableBuilder table; + + public ChatTableClientMessage( TableBuilder table ) + { + if( table.getColumns() < 0 ) throw new IllegalStateException( "Cannot send an empty table" ); + this.table = table; + } + + public ChatTableClientMessage() + { + } + + @Override + public void toBytes( @Nonnull PacketByteBuf buf ) + { + buf.writeVarInt( table.getId() ); + buf.writeVarInt( table.getColumns() ); + buf.writeBoolean( table.getHeaders() != null ); + if( table.getHeaders() != null ) + { + for( Text header : table.getHeaders() ) buf.writeText( header ); + } + + buf.writeVarInt( table.getRows().size() ); + for( Text[] row : table.getRows() ) + { + for( Text column : row ) buf.writeText( column ); + } + + buf.writeVarInt( table.getAdditional() ); + } + + @Override + public void fromBytes( @Nonnull PacketByteBuf buf ) + { + int id = buf.readVarInt(); + int columns = buf.readVarInt(); + TableBuilder table; + if( buf.readBoolean() ) + { + Text[] headers = new Text[columns]; + for( int i = 0; i < columns; i++ ) headers[i] = buf.readText(); + table = new TableBuilder( id, headers ); + } + else + { + table = new TableBuilder( id ); + } + + int rows = buf.readVarInt(); + for( int i = 0; i < rows; i++ ) + { + Text[] row = new Text[columns]; + for( int j = 0; j < columns; j++ ) row[j] = buf.readText(); + table.row( row ); + } + + table.setAdditional( buf.readVarInt() ); + this.table = table; + } + + @Override + @Environment( EnvType.CLIENT ) + public void handle( PacketContext context ) + { + ClientTableFormatter.INSTANCE.display( table ); + } +} diff --git a/remappedSrc/dan200/computercraft/shared/network/client/ComputerClientMessage.java b/remappedSrc/dan200/computercraft/shared/network/client/ComputerClientMessage.java new file mode 100644 index 000000000..630dd41c1 --- /dev/null +++ b/remappedSrc/dan200/computercraft/shared/network/client/ComputerClientMessage.java @@ -0,0 +1,57 @@ +/* + * 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.shared.network.client; + +import dan200.computercraft.ComputerCraft; +import dan200.computercraft.shared.computer.core.ClientComputer; +import dan200.computercraft.shared.network.NetworkMessage; +import javax.annotation.Nonnull; +import net.minecraft.network.PacketByteBuf; + +/** + * A packet, which performs an action on a {@link ClientComputer}. + */ +public abstract class ComputerClientMessage implements NetworkMessage +{ + private int instanceId; + + public ComputerClientMessage( int instanceId ) + { + this.instanceId = instanceId; + } + + public ComputerClientMessage() + { + } + + public int getInstanceId() + { + return instanceId; + } + + @Override + public void toBytes( @Nonnull PacketByteBuf buf ) + { + buf.writeVarInt( instanceId ); + } + + @Override + public void fromBytes( @Nonnull PacketByteBuf buf ) + { + instanceId = buf.readVarInt(); + } + + public ClientComputer getComputer() + { + ClientComputer computer = ComputerCraft.clientComputerRegistry.get( instanceId ); + if( computer == null ) + { + ComputerCraft.clientComputerRegistry.add( instanceId, computer = new ClientComputer( instanceId ) ); + } + return computer; + } +} diff --git a/remappedSrc/dan200/computercraft/shared/network/client/ComputerDataClientMessage.java b/remappedSrc/dan200/computercraft/shared/network/client/ComputerDataClientMessage.java new file mode 100644 index 000000000..7b3409466 --- /dev/null +++ b/remappedSrc/dan200/computercraft/shared/network/client/ComputerDataClientMessage.java @@ -0,0 +1,56 @@ +/* + * 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.shared.network.client; + +import dan200.computercraft.shared.computer.core.ComputerState; +import dan200.computercraft.shared.computer.core.ServerComputer; +import net.fabricmc.fabric.api.network.PacketContext; +import net.minecraft.nbt.CompoundTag; +import net.minecraft.network.PacketByteBuf; +import javax.annotation.Nonnull; + +/** + * Provides additional data about a client computer, such as its ID and current state. + */ +public class ComputerDataClientMessage extends ComputerClientMessage +{ + private ComputerState state; + private CompoundTag userData; + + public ComputerDataClientMessage( ServerComputer computer ) + { + super( computer.getInstanceID() ); + state = computer.getState(); + userData = computer.getUserData(); + } + + public ComputerDataClientMessage() + { + } + + @Override + public void toBytes( @Nonnull PacketByteBuf buf ) + { + super.toBytes( buf ); + buf.writeEnumConstant( state ); + buf.writeCompoundTag( userData ); + } + + @Override + public void fromBytes( @Nonnull PacketByteBuf buf ) + { + super.fromBytes( buf ); + state = buf.readEnumConstant( ComputerState.class ); + userData = buf.readCompoundTag(); + } + + @Override + public void handle( PacketContext context ) + { + getComputer().setState( state, userData ); + } +} diff --git a/remappedSrc/dan200/computercraft/shared/network/client/ComputerDeletedClientMessage.java b/remappedSrc/dan200/computercraft/shared/network/client/ComputerDeletedClientMessage.java new file mode 100644 index 000000000..314bea7cf --- /dev/null +++ b/remappedSrc/dan200/computercraft/shared/network/client/ComputerDeletedClientMessage.java @@ -0,0 +1,28 @@ +/* + * 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.shared.network.client; + +import dan200.computercraft.ComputerCraft; +import net.fabricmc.fabric.api.network.PacketContext; + +public class ComputerDeletedClientMessage extends ComputerClientMessage +{ + public ComputerDeletedClientMessage( int instanceId ) + { + super( instanceId ); + } + + public ComputerDeletedClientMessage() + { + } + + @Override + public void handle( PacketContext context ) + { + ComputerCraft.clientComputerRegistry.remove( getInstanceId() ); + } +} diff --git a/remappedSrc/dan200/computercraft/shared/network/client/ComputerTerminalClientMessage.java b/remappedSrc/dan200/computercraft/shared/network/client/ComputerTerminalClientMessage.java new file mode 100644 index 000000000..47773131e --- /dev/null +++ b/remappedSrc/dan200/computercraft/shared/network/client/ComputerTerminalClientMessage.java @@ -0,0 +1,47 @@ +/* + * 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.shared.network.client; + +import net.fabricmc.fabric.api.network.PacketContext; +import net.minecraft.nbt.CompoundTag; +import net.minecraft.network.PacketByteBuf; +import javax.annotation.Nonnull; + +public class ComputerTerminalClientMessage extends ComputerClientMessage +{ + private CompoundTag tag; + + public ComputerTerminalClientMessage( int instanceId, CompoundTag tag ) + { + super( instanceId ); + this.tag = tag; + } + + public ComputerTerminalClientMessage() + { + } + + @Override + public void toBytes( @Nonnull PacketByteBuf buf ) + { + super.toBytes( buf ); + buf.writeCompoundTag( tag ); // TODO: Do we need to compress this? + } + + @Override + public void fromBytes( @Nonnull PacketByteBuf buf ) + { + super.fromBytes( buf ); + tag = buf.readCompoundTag(); + } + + @Override + public void handle( PacketContext context ) + { + getComputer().readDescription( tag ); + } +} diff --git a/remappedSrc/dan200/computercraft/shared/network/client/PlayRecordClientMessage.java b/remappedSrc/dan200/computercraft/shared/network/client/PlayRecordClientMessage.java new file mode 100644 index 000000000..9a5a0e1f5 --- /dev/null +++ b/remappedSrc/dan200/computercraft/shared/network/client/PlayRecordClientMessage.java @@ -0,0 +1,88 @@ +/* + * 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.shared.network.client; + +import dan200.computercraft.shared.network.NetworkMessage; +import net.fabricmc.api.EnvType; +import net.fabricmc.api.Environment; +import net.fabricmc.fabric.api.network.PacketContext; +import net.minecraft.client.MinecraftClient; +import net.minecraft.network.PacketByteBuf; +import net.minecraft.sound.SoundEvent; +import net.minecraft.util.math.BlockPos; +import net.minecraft.util.registry.Registry; + +import javax.annotation.Nonnull; +import java.util.Objects; + +/** + * Starts or stops a record on the client, depending on if {@link #soundEvent} is {@code null}. + * + * Used by disk drives to play record items. + * + * @see dan200.computercraft.shared.peripheral.diskdrive.TileDiskDrive + */ +public class PlayRecordClientMessage implements NetworkMessage +{ + private final BlockPos pos; + private final String name; + private final SoundEvent soundEvent; + + public PlayRecordClientMessage( BlockPos pos, SoundEvent event, String name ) + { + this.pos = pos; + this.name = name; + soundEvent = event; + } + + public PlayRecordClientMessage( BlockPos pos ) + { + this.pos = pos; + name = null; + soundEvent = null; + } + + public PlayRecordClientMessage( PacketByteBuf buf ) + { + pos = buf.readBlockPos(); + if( buf.readBoolean() ) + { + name = buf.readString( Short.MAX_VALUE ); + soundEvent = Registry.SOUND_EVENT.get( buf.readIdentifier() ); + } + else + { + name = null; + soundEvent = null; + } + } + + @Override + public void toBytes( @Nonnull PacketByteBuf buf ) + { + buf.writeBlockPos( pos ); + if( soundEvent == null ) + { + buf.writeBoolean( false ); + } + else + { + buf.writeBoolean( true ); + buf.writeString( name ); + buf.writeIdentifier( Objects.requireNonNull( soundEvent.getId(), "Sound is not registered" ) ); + } + } + + @Override + @Environment( EnvType.CLIENT ) + public void handle( PacketContext context ) + { + MinecraftClient mc = MinecraftClient.getInstance(); + mc.worldRenderer.playSong( soundEvent, pos ); + if( name != null ) mc.inGameHud.setRecordPlayingOverlay( name ); + } +} diff --git a/remappedSrc/dan200/computercraft/shared/network/container/ContainerType.java b/remappedSrc/dan200/computercraft/shared/network/container/ContainerType.java new file mode 100644 index 000000000..87deb1b87 --- /dev/null +++ b/remappedSrc/dan200/computercraft/shared/network/container/ContainerType.java @@ -0,0 +1,64 @@ +/* + * 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.shared.network.container; + +import net.fabricmc.fabric.api.client.screen.ScreenProviderRegistry; +import net.fabricmc.fabric.api.container.ContainerProviderRegistry; +import net.minecraft.client.MinecraftClient; +import net.minecraft.client.gui.screen.ingame.HandledScreen; +import net.minecraft.entity.player.PlayerEntity; +import net.minecraft.entity.player.PlayerInventory; +import net.minecraft.network.PacketByteBuf; +import net.minecraft.screen.ScreenHandler; +import net.minecraft.util.Identifier; +import javax.annotation.Nonnull; +import java.util.function.BiFunction; +import java.util.function.Supplier; + +public interface ContainerType +{ + @Nonnull + Identifier getId(); + + void toBytes( PacketByteBuf buf ); + + void fromBytes( PacketByteBuf buf ); + + default void open( PlayerEntity player ) + { + ContainerProviderRegistry.INSTANCE.openContainer( getId(), player, this::toBytes ); + } + + static > void register( Supplier containerType, ContainerFactory factory ) + { + ContainerProviderRegistry.INSTANCE.registerFactory( containerType.get().getId(), ( id, type, player, packet ) -> { + T container = containerType.get(); + container.fromBytes( packet ); + return factory.apply( id, container, player ); + } ); + } + + static > void registerGui( Supplier containerType, ContainerFactory> factory ) + { + ScreenProviderRegistry.INSTANCE.registerFactory( containerType.get().getId(), ( id, type, player, packet ) -> { + T container = containerType.get(); + container.fromBytes( packet ); + return factory.apply( id, container, player ); + } ); + } + + static > void registerGui( Supplier containerType, BiFunction> factory ) + { + ScreenProviderRegistry.INSTANCE.registerFactory( containerType.get().getId(), container -> + factory.apply( container, MinecraftClient.getInstance().player.inventory ) ); + } + + interface ContainerFactory + { + R apply( int id, T input, PlayerEntity player ); + } +} diff --git a/remappedSrc/dan200/computercraft/shared/network/container/PocketComputerContainerType.java b/remappedSrc/dan200/computercraft/shared/network/container/PocketComputerContainerType.java new file mode 100644 index 000000000..890752c28 --- /dev/null +++ b/remappedSrc/dan200/computercraft/shared/network/container/PocketComputerContainerType.java @@ -0,0 +1,54 @@ +/* + * 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.shared.network.container; + +import dan200.computercraft.ComputerCraft; +import dan200.computercraft.shared.pocket.inventory.ContainerPocketComputer; +import net.minecraft.network.PacketByteBuf; +import net.minecraft.util.Hand; +import net.minecraft.util.Identifier; +import javax.annotation.Nonnull; + +/** + * Opens a pocket computer GUI based on the held item + * + * @see dan200.computercraft.shared.pocket.items.ItemPocketComputer + */ +public class PocketComputerContainerType implements ContainerType +{ + public static final Identifier ID = new Identifier( ComputerCraft.MOD_ID, "pocket_computer_gui" ); + + public Hand hand; + + public PocketComputerContainerType( Hand hand ) + { + this.hand = hand; + } + + public PocketComputerContainerType() + { + } + + @Nonnull + @Override + public Identifier getId() + { + return ID; + } + + @Override + public void toBytes( @Nonnull PacketByteBuf buf ) + { + buf.writeEnumConstant( hand ); + } + + @Override + public void fromBytes( @Nonnull PacketByteBuf buf ) + { + hand = buf.readEnumConstant( Hand.class ); + } +} diff --git a/remappedSrc/dan200/computercraft/shared/network/container/PrintoutContainerType.java b/remappedSrc/dan200/computercraft/shared/network/container/PrintoutContainerType.java new file mode 100644 index 000000000..bebc8da21 --- /dev/null +++ b/remappedSrc/dan200/computercraft/shared/network/container/PrintoutContainerType.java @@ -0,0 +1,54 @@ +/* + * 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.shared.network.container; + +import dan200.computercraft.ComputerCraft; +import dan200.computercraft.shared.common.ContainerHeldItem; +import net.minecraft.network.PacketByteBuf; +import net.minecraft.util.Hand; +import net.minecraft.util.Identifier; +import javax.annotation.Nonnull; + +/** + * Opens a printout GUI based on the currently held item + * + * @see dan200.computercraft.shared.media.items.ItemPrintout + */ +public class PrintoutContainerType implements ContainerType +{ + public static final Identifier ID = new Identifier( ComputerCraft.MOD_ID, "printout_gui" ); + + public Hand hand; + + public PrintoutContainerType( Hand hand ) + { + this.hand = hand; + } + + public PrintoutContainerType() + { + } + + @Nonnull + @Override + public Identifier getId() + { + return ID; + } + + @Override + public void toBytes( @Nonnull PacketByteBuf buf ) + { + buf.writeEnumConstant( hand ); + } + + @Override + public void fromBytes( @Nonnull PacketByteBuf buf ) + { + hand = buf.readEnumConstant( Hand.class ); + } +} diff --git a/remappedSrc/dan200/computercraft/shared/network/container/TileEntityContainerType.java b/remappedSrc/dan200/computercraft/shared/network/container/TileEntityContainerType.java new file mode 100644 index 000000000..504b0c4b0 --- /dev/null +++ b/remappedSrc/dan200/computercraft/shared/network/container/TileEntityContainerType.java @@ -0,0 +1,114 @@ +/* + * 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.shared.network.container; + +import dan200.computercraft.ComputerCraft; +import dan200.computercraft.shared.computer.inventory.ContainerComputer; +import dan200.computercraft.shared.peripheral.diskdrive.ContainerDiskDrive; +import dan200.computercraft.shared.peripheral.printer.ContainerPrinter; +import dan200.computercraft.shared.turtle.inventory.ContainerTurtle; +import net.minecraft.block.entity.BlockEntity; +import net.minecraft.entity.player.PlayerEntity; +import net.minecraft.network.PacketByteBuf; +import net.minecraft.screen.ScreenHandler; +import net.minecraft.util.Identifier; +import net.minecraft.util.math.BlockPos; + +import javax.annotation.Nonnull; + +/** + * Opens a GUI on a specific ComputerCraft BlockEntity + * + * @see dan200.computercraft.shared.peripheral.diskdrive.TileDiskDrive + * @see dan200.computercraft.shared.peripheral.printer.TilePrinter + * @see dan200.computercraft.shared.computer.blocks.TileComputer + */ +public final class TileEntityContainerType implements ContainerType +{ + private static final Identifier DISK_DRIVE = new Identifier( ComputerCraft.MOD_ID, "disk_drive" ); + private static final Identifier PRINTER = new Identifier( ComputerCraft.MOD_ID, "printer" ); + private static final Identifier COMPUTER = new Identifier( ComputerCraft.MOD_ID, "computer" ); + private static final Identifier TURTLE = new Identifier( ComputerCraft.MOD_ID, "turtle" ); + + public BlockPos pos; + private final Identifier id; + + private TileEntityContainerType( Identifier id, BlockPos pos ) + { + this.id = id; + this.pos = pos; + } + + private TileEntityContainerType( Identifier id ) + { + this.id = id; + } + + @Nonnull + @Override + public Identifier getId() + { + return id; + } + + @Override + public void toBytes( PacketByteBuf buf ) + { + buf.writeBlockPos( pos ); + } + + @Override + public void fromBytes( PacketByteBuf buf ) + { + pos = buf.readBlockPos(); + } + + public BlockEntity getTileEntity( PlayerEntity entity ) + { + return entity.world.getBlockEntity( pos ); + } + + public static TileEntityContainerType diskDrive() + { + return new TileEntityContainerType<>( DISK_DRIVE ); + } + + public static TileEntityContainerType diskDrive( BlockPos pos ) + { + return new TileEntityContainerType<>( DISK_DRIVE, pos ); + } + + public static TileEntityContainerType printer() + { + return new TileEntityContainerType<>( PRINTER ); + } + + public static TileEntityContainerType printer( BlockPos pos ) + { + return new TileEntityContainerType<>( PRINTER, pos ); + } + + public static TileEntityContainerType computer() + { + return new TileEntityContainerType<>( COMPUTER ); + } + + public static TileEntityContainerType computer( BlockPos pos ) + { + return new TileEntityContainerType<>( COMPUTER, pos ); + } + + public static TileEntityContainerType turtle() + { + return new TileEntityContainerType<>( TURTLE ); + } + + public static TileEntityContainerType turtle( BlockPos pos ) + { + return new TileEntityContainerType<>( TURTLE, pos ); + } +} diff --git a/remappedSrc/dan200/computercraft/shared/network/container/ViewComputerContainerType.java b/remappedSrc/dan200/computercraft/shared/network/container/ViewComputerContainerType.java new file mode 100644 index 000000000..4799ae79a --- /dev/null +++ b/remappedSrc/dan200/computercraft/shared/network/container/ViewComputerContainerType.java @@ -0,0 +1,72 @@ +/* + * 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.shared.network.container; + +import dan200.computercraft.ComputerCraft; +import dan200.computercraft.core.terminal.Terminal; +import dan200.computercraft.shared.computer.core.ComputerFamily; +import dan200.computercraft.shared.computer.core.ServerComputer; +import dan200.computercraft.shared.computer.inventory.ContainerViewComputer; +import net.minecraft.network.PacketByteBuf; +import net.minecraft.util.Identifier; +import javax.annotation.Nonnull; + +/** + * View an arbitrary computer on the client. + * + * @see dan200.computercraft.shared.command.CommandComputerCraft + */ +public class ViewComputerContainerType implements ContainerType +{ + public static final Identifier ID = new Identifier( ComputerCraft.MOD_ID, "view_computer_gui" ); + + public int instanceId; + public int width; + public int height; + public ComputerFamily family; + + public ViewComputerContainerType( ServerComputer computer ) + { + instanceId = computer.getInstanceID(); + Terminal terminal = computer.getTerminal(); + if( terminal != null ) + { + width = terminal.getWidth(); + height = terminal.getHeight(); + } + family = computer.getFamily(); + } + + public ViewComputerContainerType() + { + } + + @Nonnull + @Override + public Identifier getId() + { + return ID; + } + + @Override + public void toBytes( @Nonnull PacketByteBuf buf ) + { + buf.writeVarInt( instanceId ); + buf.writeVarInt( width ); + buf.writeVarInt( height ); + buf.writeEnumConstant( family ); + } + + @Override + public void fromBytes( @Nonnull PacketByteBuf buf ) + { + instanceId = buf.readVarInt(); + width = buf.readVarInt(); + height = buf.readVarInt(); + family = buf.readEnumConstant( ComputerFamily.class ); + } +} diff --git a/remappedSrc/dan200/computercraft/shared/network/server/ComputerActionServerMessage.java b/remappedSrc/dan200/computercraft/shared/network/server/ComputerActionServerMessage.java new file mode 100644 index 000000000..2b5dc9939 --- /dev/null +++ b/remappedSrc/dan200/computercraft/shared/network/server/ComputerActionServerMessage.java @@ -0,0 +1,65 @@ +/* + * 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.shared.network.server; + +import dan200.computercraft.shared.computer.core.IContainerComputer; +import dan200.computercraft.shared.computer.core.ServerComputer; +import javax.annotation.Nonnull; +import net.minecraft.network.PacketByteBuf; + +public class ComputerActionServerMessage extends ComputerServerMessage +{ + private Action action; + + public ComputerActionServerMessage( int instanceId, Action action ) + { + super( instanceId ); + this.action = action; + } + + public ComputerActionServerMessage() + { + } + + @Override + public void toBytes( @Nonnull PacketByteBuf buf ) + { + super.toBytes( buf ); + buf.writeEnumConstant( action ); + } + + @Override + public void fromBytes( @Nonnull PacketByteBuf buf ) + { + super.fromBytes( buf ); + action = buf.readEnumConstant( Action.class ); + } + + @Override + protected void handle( @Nonnull ServerComputer computer, @Nonnull IContainerComputer container ) + { + switch( action ) + { + case TURN_ON: + computer.turnOn(); + break; + case REBOOT: + computer.reboot(); + break; + case SHUTDOWN: + computer.shutdown(); + break; + } + } + + public enum Action + { + TURN_ON, + SHUTDOWN, + REBOOT + } +} diff --git a/remappedSrc/dan200/computercraft/shared/network/server/ComputerServerMessage.java b/remappedSrc/dan200/computercraft/shared/network/server/ComputerServerMessage.java new file mode 100644 index 000000000..d36af723d --- /dev/null +++ b/remappedSrc/dan200/computercraft/shared/network/server/ComputerServerMessage.java @@ -0,0 +1,61 @@ +/* + * 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.shared.network.server; + +import dan200.computercraft.ComputerCraft; +import dan200.computercraft.shared.computer.core.IContainerComputer; +import dan200.computercraft.shared.computer.core.ServerComputer; +import dan200.computercraft.shared.network.NetworkMessage; +import net.fabricmc.fabric.api.network.PacketContext; +import net.minecraft.network.PacketByteBuf; +import javax.annotation.Nonnull; + +/** + * A packet, which performs an action on a {@link ServerComputer}. + * + * This requires that the sending player is interacting with that computer via a + * {@link IContainerComputer}. + */ +public abstract class ComputerServerMessage implements NetworkMessage +{ + private int instanceId; + + public ComputerServerMessage( int instanceId ) + { + this.instanceId = instanceId; + } + + public ComputerServerMessage() + { + } + + @Override + public void toBytes( @Nonnull PacketByteBuf buf ) + { + buf.writeVarInt( instanceId ); + } + + @Override + public void fromBytes( @Nonnull PacketByteBuf buf ) + { + instanceId = buf.readVarInt(); + } + + @Override + public void handle( PacketContext context ) + { + ServerComputer computer = ComputerCraft.serverComputerRegistry.get( instanceId ); + if( computer == null ) return; + + IContainerComputer container = computer.getContainer( context.getPlayer() ); + if( container == null ) return; + + handle( computer, container ); + } + + protected abstract void handle( @Nonnull ServerComputer computer, @Nonnull IContainerComputer container ); +} diff --git a/remappedSrc/dan200/computercraft/shared/network/server/KeyEventServerMessage.java b/remappedSrc/dan200/computercraft/shared/network/server/KeyEventServerMessage.java new file mode 100644 index 000000000..ee8a5a9b1 --- /dev/null +++ b/remappedSrc/dan200/computercraft/shared/network/server/KeyEventServerMessage.java @@ -0,0 +1,64 @@ +/* + * 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.shared.network.server; + +import dan200.computercraft.shared.computer.core.IContainerComputer; +import dan200.computercraft.shared.computer.core.InputState; +import dan200.computercraft.shared.computer.core.ServerComputer; +import javax.annotation.Nonnull; +import net.minecraft.network.PacketByteBuf; + +public class KeyEventServerMessage extends ComputerServerMessage +{ + public static final int TYPE_DOWN = 0; + public static final int TYPE_REPEAT = 1; + public static final int TYPE_UP = 2; + + private int type; + private int key; + + public KeyEventServerMessage( int instanceId, int type, int key ) + { + super( instanceId ); + this.type = type; + this.key = key; + } + + public KeyEventServerMessage() + { + } + + @Override + public void toBytes( @Nonnull PacketByteBuf buf ) + { + super.toBytes( buf ); + buf.writeByte( type ); + buf.writeVarInt( key ); + } + + @Override + public void fromBytes( @Nonnull PacketByteBuf buf ) + { + super.fromBytes( buf ); + type = buf.readByte(); + key = buf.readVarInt(); + } + + @Override + protected void handle( @Nonnull ServerComputer computer, @Nonnull IContainerComputer container ) + { + InputState input = container.getInput(); + if( type == TYPE_UP ) + { + input.keyUp( key ); + } + else + { + input.keyDown( key, type == TYPE_REPEAT ); + } + } +} diff --git a/remappedSrc/dan200/computercraft/shared/network/server/MouseEventServerMessage.java b/remappedSrc/dan200/computercraft/shared/network/server/MouseEventServerMessage.java new file mode 100644 index 000000000..2ffabbf38 --- /dev/null +++ b/remappedSrc/dan200/computercraft/shared/network/server/MouseEventServerMessage.java @@ -0,0 +1,80 @@ +/* + * 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.shared.network.server; + +import dan200.computercraft.shared.computer.core.IContainerComputer; +import dan200.computercraft.shared.computer.core.InputState; +import dan200.computercraft.shared.computer.core.ServerComputer; +import javax.annotation.Nonnull; +import net.minecraft.network.PacketByteBuf; + +public class MouseEventServerMessage extends ComputerServerMessage +{ + public static final int TYPE_CLICK = 0; + public static final int TYPE_DRAG = 1; + public static final int TYPE_UP = 2; + public static final int TYPE_SCROLL = 3; + + private int type; + private int x; + private int y; + private int arg; + + public MouseEventServerMessage( int instanceId, int type, int arg, int x, int y ) + { + super( instanceId ); + this.type = type; + this.arg = arg; + this.x = x; + this.y = y; + } + + public MouseEventServerMessage() + { + } + + @Override + public void toBytes( @Nonnull PacketByteBuf buf ) + { + super.toBytes( buf ); + buf.writeByte( type ); + buf.writeVarInt( arg ); + buf.writeVarInt( x ); + buf.writeVarInt( y ); + } + + @Override + public void fromBytes( @Nonnull PacketByteBuf buf ) + { + super.fromBytes( buf ); + type = buf.readByte(); + arg = buf.readVarInt(); + x = buf.readVarInt(); + y = buf.readVarInt(); + } + + @Override + protected void handle( @Nonnull ServerComputer computer, @Nonnull IContainerComputer container ) + { + InputState input = container.getInput(); + switch( type ) + { + case TYPE_CLICK: + input.mouseClick( arg, x, y ); + break; + case TYPE_DRAG: + input.mouseDrag( arg, x, y ); + break; + case TYPE_UP: + input.mouseUp( arg, x, y ); + break; + case TYPE_SCROLL: + input.mouseScroll( arg, x, y ); + break; + } + } +} diff --git a/remappedSrc/dan200/computercraft/shared/network/server/QueueEventServerMessage.java b/remappedSrc/dan200/computercraft/shared/network/server/QueueEventServerMessage.java new file mode 100644 index 000000000..14e3f2326 --- /dev/null +++ b/remappedSrc/dan200/computercraft/shared/network/server/QueueEventServerMessage.java @@ -0,0 +1,62 @@ +/* + * 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.shared.network.server; + +import dan200.computercraft.shared.computer.core.IContainerComputer; +import dan200.computercraft.shared.computer.core.ServerComputer; +import dan200.computercraft.shared.util.NBTUtil; +import net.minecraft.nbt.CompoundTag; +import net.minecraft.network.PacketByteBuf; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +/** + * Queue an event on a {@link ServerComputer}. + * + * @see dan200.computercraft.shared.computer.core.ClientComputer#queueEvent(String) + * @see ServerComputer#queueEvent(String) + */ +public class QueueEventServerMessage extends ComputerServerMessage +{ + private String event; + private Object[] args; + + public QueueEventServerMessage( int instanceId, @Nonnull String event, @Nullable Object[] args ) + { + super( instanceId ); + this.event = event; + this.args = args; + } + + public QueueEventServerMessage() + { + } + + @Override + public void toBytes( @Nonnull PacketByteBuf buf ) + { + super.toBytes( buf ); + buf.writeString( event ); + buf.writeCompoundTag( args == null ? null : NBTUtil.encodeObjects( args ) ); + } + + @Override + public void fromBytes( @Nonnull PacketByteBuf buf ) + { + super.fromBytes( buf ); + event = buf.readString( Short.MAX_VALUE ); + + CompoundTag args = buf.readCompoundTag(); + this.args = args == null ? null : NBTUtil.decodeObjects( args ); + } + + @Override + protected void handle( @Nonnull ServerComputer computer, @Nonnull IContainerComputer container ) + { + computer.queueEvent( event, args ); + } +} diff --git a/remappedSrc/dan200/computercraft/shared/network/server/RequestComputerMessage.java b/remappedSrc/dan200/computercraft/shared/network/server/RequestComputerMessage.java new file mode 100644 index 000000000..aae3f4eee --- /dev/null +++ b/remappedSrc/dan200/computercraft/shared/network/server/RequestComputerMessage.java @@ -0,0 +1,47 @@ +/* + * 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.shared.network.server; + +import dan200.computercraft.ComputerCraft; +import dan200.computercraft.shared.computer.core.ServerComputer; +import dan200.computercraft.shared.network.NetworkMessage; +import net.fabricmc.fabric.api.network.PacketContext; +import net.minecraft.network.PacketByteBuf; +import javax.annotation.Nonnull; + +public class RequestComputerMessage implements NetworkMessage +{ + private int instance; + + public RequestComputerMessage( int instance ) + { + this.instance = instance; + } + + public RequestComputerMessage() + { + } + + @Override + public void toBytes( @Nonnull PacketByteBuf buf ) + { + buf.writeVarInt( instance ); + } + + @Override + public void fromBytes( @Nonnull PacketByteBuf buf ) + { + instance = buf.readVarInt(); + } + + @Override + public void handle( PacketContext context ) + { + ServerComputer computer = ComputerCraft.serverComputerRegistry.get( instance ); + if( computer != null ) computer.sendComputerState( context.getPlayer() ); + } +} diff --git a/remappedSrc/dan200/computercraft/shared/peripheral/commandblock/CommandBlockPeripheral.java b/remappedSrc/dan200/computercraft/shared/peripheral/commandblock/CommandBlockPeripheral.java new file mode 100644 index 000000000..1b5f2a379 --- /dev/null +++ b/remappedSrc/dan200/computercraft/shared/peripheral/commandblock/CommandBlockPeripheral.java @@ -0,0 +1,92 @@ +/* + * 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.shared.peripheral.commandblock; + +import dan200.computercraft.api.lua.ILuaContext; +import dan200.computercraft.api.lua.LuaException; +import dan200.computercraft.api.peripheral.IComputerAccess; +import dan200.computercraft.api.peripheral.IPeripheral; +import net.minecraft.block.entity.CommandBlockBlockEntity; + +import javax.annotation.Nonnull; + +import static dan200.computercraft.core.apis.ArgumentHelper.getString; + +public class CommandBlockPeripheral implements IPeripheral +{ + private final CommandBlockBlockEntity m_commandBlock; + + public CommandBlockPeripheral( CommandBlockBlockEntity commandBlock ) + { + m_commandBlock = commandBlock; + } + + // IPeripheral methods + + @Nonnull + @Override + public String getType() + { + return "command"; + } + + @Nonnull + @Override + public String[] getMethodNames() + { + return new String[] { + "getCommand", + "setCommand", + "runCommand", + }; + } + + @Override + public Object[] callMethod( @Nonnull IComputerAccess computer, @Nonnull ILuaContext context, int method, @Nonnull final Object[] arguments ) throws LuaException, InterruptedException + { + switch( method ) + { + case 0: // getCommand + return context.executeMainThreadTask( () -> new Object[] { + m_commandBlock.getCommandExecutor().getCommand() + } ); + case 1: + { + // setCommand + final String command = getString( arguments, 0 ); + context.issueMainThreadTask( () -> + { + m_commandBlock.getCommandExecutor().setCommand( command ); + m_commandBlock.getCommandExecutor().markDirty(); + return null; + } ); + return null; + } + case 2: // runCommand + return context.executeMainThreadTask( () -> + { + m_commandBlock.getCommandExecutor().execute( m_commandBlock.getWorld() ); + int result = m_commandBlock.getCommandExecutor().getSuccessCount(); + if( result > 0 ) + { + return new Object[] { true }; + } + else + { + return new Object[] { false, "Command failed" }; + } + } ); + } + return null; + } + + @Override + public boolean equals( IPeripheral other ) + { + return other != null && other.getClass() == getClass(); + } +} diff --git a/remappedSrc/dan200/computercraft/shared/peripheral/diskdrive/BlockDiskDrive.java b/remappedSrc/dan200/computercraft/shared/peripheral/diskdrive/BlockDiskDrive.java new file mode 100644 index 000000000..070f64afb --- /dev/null +++ b/remappedSrc/dan200/computercraft/shared/peripheral/diskdrive/BlockDiskDrive.java @@ -0,0 +1,61 @@ +/* + * 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.shared.peripheral.diskdrive; + +import dan200.computercraft.shared.common.BlockGeneric; +import net.minecraft.block.Block; +import net.minecraft.block.BlockState; +import net.minecraft.block.entity.BlockEntity; +import net.minecraft.entity.LivingEntity; +import net.minecraft.item.ItemPlacementContext; +import net.minecraft.item.ItemStack; +import net.minecraft.state.StateManager; +import net.minecraft.state.property.DirectionProperty; +import net.minecraft.state.property.EnumProperty; +import net.minecraft.state.property.Properties; +import net.minecraft.util.math.BlockPos; +import net.minecraft.util.math.Direction; +import net.minecraft.world.World; + +import javax.annotation.Nullable; + +public class BlockDiskDrive extends BlockGeneric +{ + static final DirectionProperty FACING = Properties.HORIZONTAL_FACING; + static final EnumProperty STATE = EnumProperty.of( "state", DiskDriveState.class ); + + public BlockDiskDrive( Settings settings ) + { + super( settings, TileDiskDrive.FACTORY ); + setDefaultState( getStateManager().getDefaultState() + .with( FACING, Direction.NORTH ) + .with( STATE, DiskDriveState.EMPTY ) ); + } + + @Override + protected void appendProperties( StateManager.Builder properties ) + { + properties.add( FACING, STATE ); + } + + @Nullable + @Override + public BlockState getPlacementState( ItemPlacementContext placement ) + { + return getDefaultState().with( FACING, placement.getPlayerFacing().getOpposite() ); + } + + @Override + public void onPlaced( World world, BlockPos pos, BlockState state, LivingEntity placer, ItemStack stack ) + { + if( stack.hasCustomName() ) + { + BlockEntity tileentity = world.getBlockEntity( pos ); + if( tileentity instanceof TileDiskDrive ) ((TileDiskDrive) tileentity).customName = stack.getName(); + } + } +} diff --git a/remappedSrc/dan200/computercraft/shared/peripheral/diskdrive/ContainerDiskDrive.java b/remappedSrc/dan200/computercraft/shared/peripheral/diskdrive/ContainerDiskDrive.java new file mode 100644 index 000000000..5617dc1de --- /dev/null +++ b/remappedSrc/dan200/computercraft/shared/peripheral/diskdrive/ContainerDiskDrive.java @@ -0,0 +1,88 @@ +/* + * 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.shared.peripheral.diskdrive; + +import net.minecraft.entity.player.PlayerEntity; +import net.minecraft.entity.player.PlayerInventory; +import net.minecraft.inventory.Inventory; +import net.minecraft.inventory.SimpleInventory; +import net.minecraft.item.ItemStack; +import net.minecraft.screen.ScreenHandler; +import net.minecraft.screen.slot.Slot; +import javax.annotation.Nonnull; + +public class ContainerDiskDrive extends ScreenHandler +{ + private final Inventory m_diskDrive; + + public ContainerDiskDrive( int id, PlayerInventory player ) + { + this( id, player, new SimpleInventory( TileDiskDrive.INVENTORY_SIZE ) ); + } + + public ContainerDiskDrive( int id, PlayerInventory playerInventory, Inventory diskDrive ) + { + super( null, id ); + + m_diskDrive = diskDrive; + + addSlot( new Slot( m_diskDrive, 0, 8 + 4 * 18, 35 ) ); + + for( int y = 0; y < 3; y++ ) + { + for( int x = 0; x < 9; x++ ) + { + addSlot( new Slot( playerInventory, x + y * 9 + 9, 8 + x * 18, 84 + y * 18 ) ); + } + } + + for( int x = 0; x < 9; x++ ) + { + addSlot( new Slot( playerInventory, x, 8 + x * 18, 142 ) ); + } + } + + @Override + public boolean canUse( @Nonnull PlayerEntity player ) + { + return m_diskDrive.canPlayerUse( player ); + } + + @Override + public ItemStack transferSlot( PlayerEntity player, int slotIndex ) + { + Slot slot = slots.get( slotIndex ); + if( slot == null || !slot.hasStack() ) return ItemStack.EMPTY; + + ItemStack existing = slot.getStack(); + ItemStack result = existing.copy(); + if( slotIndex == 0 ) + { + // Insert into player inventory + if( !insertItem( existing, 1, 37, true ) ) return ItemStack.EMPTY; + } + else + { + // Insert into drive inventory + if( !insertItem( existing, 0, 1, false ) ) return ItemStack.EMPTY; + } + + if( existing.isEmpty() ) + { + slot.setStack( ItemStack.EMPTY ); + } + else + { + slot.markDirty(); + } + + if( existing.getCount() == result.getCount() ) return ItemStack.EMPTY; + + slot.onTakeItem( player, existing ); + return result; + } +} diff --git a/remappedSrc/dan200/computercraft/shared/peripheral/diskdrive/DiskDrivePeripheral.java b/remappedSrc/dan200/computercraft/shared/peripheral/diskdrive/DiskDrivePeripheral.java new file mode 100644 index 000000000..1623a1548 --- /dev/null +++ b/remappedSrc/dan200/computercraft/shared/peripheral/diskdrive/DiskDrivePeripheral.java @@ -0,0 +1,142 @@ +/* + * 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.shared.peripheral.diskdrive; + +import dan200.computercraft.api.lua.ILuaContext; +import dan200.computercraft.api.lua.LuaException; +import dan200.computercraft.api.media.IMedia; +import dan200.computercraft.api.peripheral.IComputerAccess; +import dan200.computercraft.api.peripheral.IPeripheral; +import dan200.computercraft.shared.media.items.ItemDisk; +import dan200.computercraft.shared.util.StringUtil; +import net.minecraft.item.ItemStack; + +import javax.annotation.Nonnull; + +import static dan200.computercraft.core.apis.ArgumentHelper.optString; + +public class DiskDrivePeripheral implements IPeripheral +{ + private final TileDiskDrive m_diskDrive; + + public DiskDrivePeripheral( TileDiskDrive diskDrive ) + { + m_diskDrive = diskDrive; + } + + @Nonnull + @Override + public String getType() + { + return "drive"; + } + + @Nonnull + @Override + public String[] getMethodNames() + { + return new String[] { + "isDiskPresent", + "getDiskLabel", + "setDiskLabel", + "hasData", + "getMountPath", + "hasAudio", + "getAudioTitle", + "playAudio", + "stopAudio", + "ejectDisk", + "getDiskID" + }; + } + + @Override + public Object[] callMethod( @Nonnull IComputerAccess computer, @Nonnull ILuaContext context, int method, @Nonnull Object[] arguments ) throws LuaException + { + switch( method ) + { + case 0: // isDiskPresent + return new Object[] { !m_diskDrive.getDiskStack().isEmpty() }; + case 1: // getDiskLabel + { + IMedia media = m_diskDrive.getDiskMedia(); + return media == null ? null : new Object[] { media.getLabel( m_diskDrive.getDiskStack() ) }; + } + case 2: // setDiskLabel + { + String label = optString( arguments, 0, null ); + + IMedia media = m_diskDrive.getDiskMedia(); + if( media == null ) return null; + + ItemStack disk = m_diskDrive.getDiskStack(); + label = StringUtil.normaliseLabel( label ); + if( !media.setLabel( disk, label ) ) throw new LuaException( "Disk label cannot be changed" ); + m_diskDrive.setDiskStack( disk ); + return null; + } + case 3: // hasData + return new Object[] { m_diskDrive.getDiskMountPath( computer ) != null }; + case 4: // getMountPath + return new Object[] { m_diskDrive.getDiskMountPath( computer ) }; + case 5: + { + // hasAudio + IMedia media = m_diskDrive.getDiskMedia(); + return new Object[] { media != null && media.getAudio( m_diskDrive.getDiskStack() ) != null }; + } + case 6: + { + // getAudioTitle + IMedia media = m_diskDrive.getDiskMedia(); + return new Object[] { media != null ? media.getAudioTitle( m_diskDrive.getDiskStack() ) : false }; + } + case 7: // playAudio + m_diskDrive.playDiskAudio(); + return null; + case 8: // stopAudio + m_diskDrive.stopDiskAudio(); + return null; + case 9: // eject + m_diskDrive.ejectDisk(); + return null; + case 10: // getDiskID + { + ItemStack disk = m_diskDrive.getDiskStack(); + return disk.getItem() instanceof ItemDisk ? new Object[] { ItemDisk.getDiskID( disk ) } : null; + } + default: + return null; + } + } + + @Override + public void attach( @Nonnull IComputerAccess computer ) + { + m_diskDrive.mount( computer ); + } + + @Override + public void detach( @Nonnull IComputerAccess computer ) + { + m_diskDrive.unmount( computer ); + } + + @Override + public boolean equals( IPeripheral other ) + { + if( this == other ) return true; + return other instanceof DiskDrivePeripheral && ((DiskDrivePeripheral) other).m_diskDrive == m_diskDrive; + } + + @Nonnull + @Override + public Object getTarget() + { + return m_diskDrive; + } +} diff --git a/remappedSrc/dan200/computercraft/shared/peripheral/diskdrive/DiskDriveState.java b/remappedSrc/dan200/computercraft/shared/peripheral/diskdrive/DiskDriveState.java new file mode 100644 index 000000000..1ad092171 --- /dev/null +++ b/remappedSrc/dan200/computercraft/shared/peripheral/diskdrive/DiskDriveState.java @@ -0,0 +1,30 @@ +/* + * 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.shared.peripheral.diskdrive; + +import net.minecraft.util.StringIdentifiable; + +public enum DiskDriveState implements StringIdentifiable +{ + EMPTY( "empty" ), + FULL( "full" ), + INVALID( "invalid" ); + + private final String name; + + DiskDriveState( String name ) + { + this.name = name; + } + + + @Override + public String asString() + { + return name; + } +} diff --git a/remappedSrc/dan200/computercraft/shared/peripheral/diskdrive/TileDiskDrive.java b/remappedSrc/dan200/computercraft/shared/peripheral/diskdrive/TileDiskDrive.java new file mode 100644 index 000000000..f9edb3e17 --- /dev/null +++ b/remappedSrc/dan200/computercraft/shared/peripheral/diskdrive/TileDiskDrive.java @@ -0,0 +1,557 @@ +/* + * 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.shared.peripheral.diskdrive; + +import dan200.computercraft.ComputerCraft; +import dan200.computercraft.api.filesystem.IMount; +import dan200.computercraft.api.filesystem.IWritableMount; +import dan200.computercraft.api.media.IMedia; +import dan200.computercraft.api.peripheral.IComputerAccess; +import dan200.computercraft.api.peripheral.IPeripheral; +import dan200.computercraft.api.peripheral.IPeripheralTile; +import dan200.computercraft.shared.MediaProviders; +import dan200.computercraft.shared.common.TileGeneric; +import dan200.computercraft.shared.network.Containers; +import dan200.computercraft.shared.util.DefaultInventory; +import dan200.computercraft.shared.util.InventoryUtil; +import dan200.computercraft.shared.util.NamedBlockEntityType; +import dan200.computercraft.shared.util.RecordUtil; +import net.minecraft.block.BlockState; +import net.minecraft.block.entity.BlockEntityType; +import net.minecraft.entity.ItemEntity; +import net.minecraft.entity.player.PlayerEntity; +import net.minecraft.item.ItemStack; +import net.minecraft.nbt.CompoundTag; +import net.minecraft.sound.SoundEvent; +import net.minecraft.text.LiteralText; +import net.minecraft.text.Text; +import net.minecraft.util.Hand; +import net.minecraft.util.Identifier; +import net.minecraft.util.Nameable; +import net.minecraft.util.Tickable; +import net.minecraft.util.hit.BlockHitResult; +import net.minecraft.util.math.BlockPos; +import net.minecraft.util.math.Direction; + +import javax.annotation.Nonnull; +import javax.annotation.Nullable; +import java.util.HashMap; +import java.util.Map; +import java.util.Set; + +public final class TileDiskDrive extends TileGeneric implements DefaultInventory, Tickable, IPeripheralTile, Nameable +{ + private static final String NBT_NAME = "CustomName"; + private static final String NBT_ITEM = "Item"; + + public static final NamedBlockEntityType FACTORY = NamedBlockEntityType.create( + new Identifier( ComputerCraft.MOD_ID, "disk_drive" ), + TileDiskDrive::new + ); + + public static final int INVENTORY_SIZE = 1; + + private static class MountInfo + { + String mountPath; + } + + Text customName; + + private final Map m_computers = new HashMap<>(); + + @Nonnull + private ItemStack m_diskStack = ItemStack.EMPTY; + private IMount m_diskMount = null; + + private boolean m_recordQueued = false; + private boolean m_recordPlaying = false; + private boolean m_restartRecord = false; + private boolean m_ejectQueued; + + private TileDiskDrive( BlockEntityType type ) + { + super( type ); + } + + @Override + public void destroy() + { + if( m_recordPlaying ) stopRecord(); + } + + @Override + public boolean onActivate( PlayerEntity player, Hand hand, BlockHitResult hit ) + { + if( player.isSneaking() ) + { + // Try to put a disk into the drive + ItemStack disk = player.getStackInHand( hand ); + if( disk.isEmpty() ) return false; + if( !getWorld().isClient && getStack( 0 ).isEmpty() && MediaProviders.get( disk ) != null ) + { + setDiskStack( disk ); + player.setStackInHand( hand, ItemStack.EMPTY ); + } + return true; + } + else + { + // Open the GUI + if( !getWorld().isClient ) Containers.openDiskDriveGUI( player, this ); + return true; + } + } + + public Direction getDirection() + { + return getCachedState().get( BlockDiskDrive.FACING ); + } + + @Override + public void fromTag( CompoundTag nbt ) + { + super.fromTag( nbt ); + customName = nbt.contains( NBT_NAME ) ? LiteralText.Serializer.fromJson( nbt.getString( NBT_NAME ) ) : null; + if( nbt.contains( NBT_ITEM ) ) + { + CompoundTag item = nbt.getCompound( NBT_ITEM ); + m_diskStack = ItemStack.fromTag( item ); + m_diskMount = null; + } + } + + @Nonnull + @Override + public CompoundTag toTag( CompoundTag nbt ) + { + if( customName != null ) nbt.putString( NBT_NAME, LiteralText.Serializer.toJson( customName ) ); + + if( !m_diskStack.isEmpty() ) + { + CompoundTag item = new CompoundTag(); + m_diskStack.toTag( item ); + nbt.put( NBT_ITEM, item ); + } + return super.toTag( nbt ); + } + + @Override + public void tick() + { + // Ejection + if( m_ejectQueued ) + { + ejectContents( false ); + m_ejectQueued = false; + } + + // Music + synchronized( this ) + { + if( !world.isClient && m_recordPlaying != m_recordQueued || m_restartRecord ) + { + m_restartRecord = false; + if( m_recordQueued ) + { + IMedia contents = getDiskMedia(); + SoundEvent record = contents != null ? contents.getAudio( m_diskStack ) : null; + if( record != null ) + { + m_recordPlaying = true; + playRecord(); + } + else + { + m_recordQueued = false; + } + } + else + { + stopRecord(); + m_recordPlaying = false; + } + } + } + } + + // Inventory implementation + + @Override + public int size() + { + return INVENTORY_SIZE; + } + + @Override + public boolean isEmpty() + { + return m_diskStack.isEmpty(); + } + + @Override + public ItemStack getStack( int slot ) + { + return m_diskStack; + } + + @Nonnull + @Override + public ItemStack removeStack( int slot ) + { + ItemStack result = m_diskStack; + m_diskStack = ItemStack.EMPTY; + m_diskMount = null; + + return result; + } + + @Nonnull + @Override + public ItemStack removeStack( int slot, int count ) + { + if( m_diskStack.isEmpty() ) return ItemStack.EMPTY; + + if( m_diskStack.getCount() <= count ) + { + ItemStack disk = m_diskStack; + setStack( slot, ItemStack.EMPTY ); + return disk; + } + + ItemStack part = m_diskStack.split( count ); + setStack( slot, m_diskStack.isEmpty() ? ItemStack.EMPTY : m_diskStack ); + return part; + } + + @Override + public void setStack( int slot, @Nonnull ItemStack stack ) + { + if( getWorld().isClient ) + { + m_diskStack = stack; + m_diskMount = null; + markDirty(); + return; + } + + synchronized( this ) + { + if( InventoryUtil.areItemsStackable( stack, m_diskStack ) ) + { + m_diskStack = stack; + return; + } + + // Unmount old disk + if( !m_diskStack.isEmpty() ) + { + // TODO: Is this iteration thread safe? + Set computers = m_computers.keySet(); + for( IComputerAccess computer : computers ) unmountDisk( computer ); + } + + // Stop music + if( m_recordPlaying ) + { + stopRecord(); + m_recordPlaying = false; + m_recordQueued = false; + } + + // Swap disk over + m_diskStack = stack; + m_diskMount = null; + markDirty(); + + // Mount new disk + if( !m_diskStack.isEmpty() ) + { + Set computers = m_computers.keySet(); + for( IComputerAccess computer : computers ) mountDisk( computer ); + } + } + } + + @Override + public void markDirty() + { + if( !world.isClient ) updateBlockState(); + super.markDirty(); + } + + @Override + public boolean canPlayerUse( PlayerEntity player ) + { + return isUsable( player, false ); + } + + @Override + public void clear() + { + setStack( 0, ItemStack.EMPTY ); + } + + @Override + public IPeripheral getPeripheral( @Nonnull Direction side ) + { + return new DiskDrivePeripheral( this ); + } + + @Nonnull + public ItemStack getDiskStack() + { + return getStack( 0 ); + } + + public void setDiskStack( @Nonnull ItemStack stack ) + { + setStack( 0, stack ); + } + + public IMedia getDiskMedia() + { + return MediaProviders.get( getDiskStack() ); + } + + public String getDiskMountPath( IComputerAccess computer ) + { + synchronized( this ) + { + if( m_computers.containsKey( computer ) ) + { + MountInfo info = m_computers.get( computer ); + return info.mountPath; + } + } + return null; + } + + public void mount( IComputerAccess computer ) + { + synchronized( this ) + { + m_computers.put( computer, new MountInfo() ); + mountDisk( computer ); + } + } + + public void unmount( IComputerAccess computer ) + { + synchronized( this ) + { + unmountDisk( computer ); + m_computers.remove( computer ); + } + } + + public void playDiskAudio() + { + synchronized( this ) + { + IMedia media = getDiskMedia(); + if( media != null && media.getAudioTitle( m_diskStack ) != null ) + { + m_recordQueued = true; + m_restartRecord = m_recordPlaying; + } + } + } + + public void stopDiskAudio() + { + synchronized( this ) + { + m_recordQueued = false; + m_restartRecord = false; + } + } + + public void ejectDisk() + { + synchronized( this ) + { + m_ejectQueued = true; + } + } + + // private methods + + private synchronized void mountDisk( IComputerAccess computer ) + { + if( !m_diskStack.isEmpty() ) + { + MountInfo info = m_computers.get( computer ); + IMedia contents = getDiskMedia(); + if( contents != null ) + { + if( m_diskMount == null ) + { + m_diskMount = contents.createDataMount( m_diskStack, getWorld() ); + } + if( m_diskMount != null ) + { + if( m_diskMount instanceof IWritableMount ) + { + // Try mounting at the lowest numbered "disk" name we can + int n = 1; + while( info.mountPath == null ) + { + info.mountPath = computer.mountWritable( n == 1 ? "disk" : "disk" + n, (IWritableMount) m_diskMount ); + n++; + } + } + else + { + // Try mounting at the lowest numbered "disk" name we can + int n = 1; + while( info.mountPath == null ) + { + info.mountPath = computer.mount( n == 1 ? "disk" : "disk" + n, m_diskMount ); + n++; + } + } + } + else + { + info.mountPath = null; + } + } + computer.queueEvent( "disk", new Object[] { computer.getAttachmentName() } ); + } + } + + private synchronized void unmountDisk( IComputerAccess computer ) + { + if( !m_diskStack.isEmpty() ) + { + MountInfo info = m_computers.get( computer ); + assert info != null; + if( info.mountPath != null ) + { + computer.unmount( info.mountPath ); + info.mountPath = null; + } + computer.queueEvent( "disk_eject", new Object[] { computer.getAttachmentName() } ); + } + } + + private void updateBlockState() + { + if( removed ) return; + + if( !m_diskStack.isEmpty() ) + { + IMedia contents = getDiskMedia(); + updateBlockState( contents != null ? DiskDriveState.FULL : DiskDriveState.INVALID ); + } + else + { + updateBlockState( DiskDriveState.EMPTY ); + } + } + + private void updateBlockState( DiskDriveState state ) + { + BlockState blockState = getCachedState(); + if( blockState.get( BlockDiskDrive.STATE ) == state ) return; + + getWorld().setBlockState( getPos(), blockState.with( BlockDiskDrive.STATE, state ) ); + } + + private synchronized void ejectContents( boolean destroyed ) + { + if( getWorld().isClient || m_diskStack.isEmpty() ) return; + + // Remove the disks from the inventory + ItemStack disks = m_diskStack; + setDiskStack( ItemStack.EMPTY ); + + // Spawn the item in the world + int xOff = 0; + int zOff = 0; + if( !destroyed ) + { + Direction dir = getDirection(); + xOff = dir.getOffsetX(); + zOff = dir.getOffsetZ(); + } + + BlockPos pos = getPos(); + double x = pos.getX() + 0.5 + xOff * 0.5; + double y = pos.getY() + 0.75; + double z = pos.getZ() + 0.5 + zOff * 0.5; + ItemEntity entityitem = new ItemEntity( getWorld(), x, y, z, disks ); + entityitem.setVelocity( xOff * 0.15, 0.0, zOff * 0.15 ); + + getWorld().spawnEntity( entityitem ); + if( !destroyed ) getWorld().syncGlobalEvent( 1000, getPos(), 0 ); + } + + @Override + protected void readDescription( @Nonnull CompoundTag nbt ) + { + super.readDescription( nbt ); + customName = nbt.contains( NBT_NAME ) ? LiteralText.Serializer.fromJson( nbt.getString( NBT_NAME ) ) : null; + m_diskStack = nbt.contains( NBT_ITEM ) ? ItemStack.fromTag( nbt.getCompound( NBT_ITEM ) ) : ItemStack.EMPTY; + updateBlock(); + } + + @Override + protected void writeDescription( @Nonnull CompoundTag nbt ) + { + super.writeDescription( nbt ); + if( customName != null ) nbt.putString( NBT_NAME, LiteralText.Serializer.toJson( customName ) ); + if( !m_diskStack.isEmpty() ) + { + CompoundTag item = new CompoundTag(); + m_diskStack.toTag( item ); + nbt.put( NBT_ITEM, item ); + } + } + + // Private methods + + private void playRecord() + { + IMedia contents = getDiskMedia(); + SoundEvent record = contents != null ? contents.getAudio( m_diskStack ) : null; + if( record != null ) + { + RecordUtil.playRecord( record, contents.getAudioTitle( m_diskStack ), getWorld(), getPos() ); + } + else + { + RecordUtil.playRecord( null, null, getWorld(), getPos() ); + } + } + + private void stopRecord() + { + RecordUtil.playRecord( null, null, getWorld(), getPos() ); + } + + @Override + public boolean hasCustomName() + { + return customName != null; + } + + @Nullable + @Override + public Text getCustomName() + { + return customName; + } + + @Nonnull + @Override + public Text getName() + { + return customName != null ? customName : getCachedState().getBlock().getName(); + } +} diff --git a/remappedSrc/dan200/computercraft/shared/peripheral/modem/ModemPeripheral.java b/remappedSrc/dan200/computercraft/shared/peripheral/modem/ModemPeripheral.java new file mode 100644 index 000000000..0c9f6453a --- /dev/null +++ b/remappedSrc/dan200/computercraft/shared/peripheral/modem/ModemPeripheral.java @@ -0,0 +1,236 @@ +/* + * 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.shared.peripheral.modem; + +import dan200.computercraft.api.lua.ILuaContext; +import dan200.computercraft.api.lua.LuaException; +import dan200.computercraft.api.network.IPacketNetwork; +import dan200.computercraft.api.network.IPacketReceiver; +import dan200.computercraft.api.network.IPacketSender; +import dan200.computercraft.api.network.Packet; +import dan200.computercraft.api.peripheral.IComputerAccess; +import dan200.computercraft.api.peripheral.IPeripheral; +import net.minecraft.util.math.Vec3d; +import net.minecraft.world.World; + +import javax.annotation.Nonnull; +import java.util.HashSet; +import java.util.Set; + +import static dan200.computercraft.core.apis.ArgumentHelper.getInt; + +public abstract class ModemPeripheral implements IPeripheral, IPacketSender, IPacketReceiver +{ + private IPacketNetwork m_network; + private final Set m_computers = new HashSet<>( 1 ); + private final ModemState m_state; + + protected ModemPeripheral( ModemState state ) + { + m_state = state; + } + + public ModemState getModemState() + { + return m_state; + } + + private synchronized void setNetwork( IPacketNetwork network ) + { + if( m_network == network ) return; + + // Leave old network + if( m_network != null ) m_network.removeReceiver( this ); + + // Set new network + m_network = network; + + // Join new network + if( m_network != null ) m_network.addReceiver( this ); + } + + protected void switchNetwork() + { + setNetwork( getNetwork() ); + } + + public void destroy() + { + setNetwork( null ); + } + + @Override + public void receiveSameDimension( @Nonnull Packet packet, double distance ) + { + if( packet.getSender() == this || !m_state.isOpen( packet.getChannel() ) ) return; + + synchronized( m_computers ) + { + for( IComputerAccess computer : m_computers ) + { + computer.queueEvent( "modem_message", new Object[] { + computer.getAttachmentName(), packet.getChannel(), packet.getReplyChannel(), packet.getPayload(), distance + } ); + } + } + } + + @Override + public void receiveDifferentDimension( @Nonnull Packet packet ) + { + if( packet.getSender() == this || !m_state.isOpen( packet.getChannel() ) ) return; + + synchronized( m_computers ) + { + for( IComputerAccess computer : m_computers ) + { + computer.queueEvent( "modem_message", new Object[] { + computer.getAttachmentName(), packet.getChannel(), packet.getReplyChannel(), packet.getPayload() + } ); + } + } + } + + protected abstract IPacketNetwork getNetwork(); + + // IPeripheral implementation + + @Nonnull + @Override + public String getType() + { + return "modem"; + } + + @Nonnull + @Override + public String[] getMethodNames() + { + return new String[] { + "open", + "isOpen", + "close", + "closeAll", + "transmit", + "isWireless", + }; + } + + private static int parseChannel( Object[] arguments, int index ) throws LuaException + { + int channel = getInt( arguments, index ); + if( channel < 0 || channel > 65535 ) + { + throw new LuaException( "Expected number in range 0-65535" ); + } + return channel; + } + + @Override + public Object[] callMethod( @Nonnull IComputerAccess computer, @Nonnull ILuaContext context, int method, @Nonnull Object[] arguments ) throws LuaException, InterruptedException + { + switch( method ) + { + case 0: + { + // open + int channel = parseChannel( arguments, 0 ); + m_state.open( channel ); + return null; + } + case 1: + { + // isOpen + int channel = parseChannel( arguments, 0 ); + return new Object[] { m_state.isOpen( channel ) }; + } + case 2: + { + // close + int channel = parseChannel( arguments, 0 ); + m_state.close( channel ); + return null; + } + case 3: // closeAll + m_state.closeAll(); + return null; + case 4: + { + // transmit + int channel = parseChannel( arguments, 0 ); + int replyChannel = parseChannel( arguments, 1 ); + Object payload = arguments.length > 2 ? arguments[2] : null; + World world = getWorld(); + Vec3d position = getPosition(); + IPacketNetwork network = m_network; + if( world != null && position != null && network != null ) + { + Packet packet = new Packet( channel, replyChannel, payload, this ); + if( isInterdimensional() ) + { + network.transmitInterdimensional( packet ); + } + else + { + network.transmitSameDimension( packet, getRange() ); + } + } + return null; + } + case 5: + { + // isWireless + IPacketNetwork network = m_network; + return new Object[] { network != null && network.isWireless() }; + } + default: + return null; + } + } + + @Override + public synchronized void attach( @Nonnull IComputerAccess computer ) + { + synchronized( m_computers ) + { + m_computers.add( computer ); + } + + setNetwork( getNetwork() ); + } + + @Override + public synchronized void detach( @Nonnull IComputerAccess computer ) + { + boolean empty; + synchronized( m_computers ) + { + m_computers.remove( computer ); + empty = m_computers.isEmpty(); + } + + if( empty ) setNetwork( null ); + } + + @Nonnull + @Override + public String getSenderID() + { + synchronized( m_computers ) + { + if( m_computers.size() != 1 ) + { + return "unknown"; + } + else + { + IComputerAccess computer = m_computers.iterator().next(); + return computer.getID() + "_" + computer.getAttachmentName(); + } + } + } +} diff --git a/remappedSrc/dan200/computercraft/shared/peripheral/modem/ModemShapes.java b/remappedSrc/dan200/computercraft/shared/peripheral/modem/ModemShapes.java new file mode 100644 index 000000000..68385ae92 --- /dev/null +++ b/remappedSrc/dan200/computercraft/shared/peripheral/modem/ModemShapes.java @@ -0,0 +1,32 @@ +/* + * 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.shared.peripheral.modem; + +import net.minecraft.util.math.Direction; +import net.minecraft.util.shape.VoxelShape; +import net.minecraft.util.shape.VoxelShapes; + +import javax.annotation.Nonnull; + +public final class ModemShapes +{ + private static final VoxelShape[] BOXES = new VoxelShape[] { + VoxelShapes.cuboid( 0.125, 0.0, 0.125, 0.875, 0.1875, 0.875 ), // Down + VoxelShapes.cuboid( 0.125, 0.8125, 0.125, 0.875, 1.0, 0.875 ), // Up + VoxelShapes.cuboid( 0.125, 0.125, 0.0, 0.875, 0.875, 0.1875 ), // North + VoxelShapes.cuboid( 0.125, 0.125, 0.8125, 0.875, 0.875, 1.0 ), // South + VoxelShapes.cuboid( 0.0, 0.125, 0.125, 0.1875, 0.875, 0.875 ), // West + VoxelShapes.cuboid( 0.8125, 0.125, 0.125, 1.0, 0.875, 0.875 ), // East + }; + + @Nonnull + public static VoxelShape getBounds( Direction facing ) + { + int direction = facing.ordinal(); + return direction < BOXES.length ? BOXES[direction] : VoxelShapes.fullCube(); + } +} diff --git a/remappedSrc/dan200/computercraft/shared/peripheral/modem/ModemState.java b/remappedSrc/dan200/computercraft/shared/peripheral/modem/ModemState.java new file mode 100644 index 000000000..ba4cfd790 --- /dev/null +++ b/remappedSrc/dan200/computercraft/shared/peripheral/modem/ModemState.java @@ -0,0 +1,88 @@ +/* + * 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.shared.peripheral.modem; + +import dan200.computercraft.api.lua.LuaException; +import it.unimi.dsi.fastutil.ints.IntOpenHashSet; +import it.unimi.dsi.fastutil.ints.IntSet; + +import java.util.concurrent.atomic.AtomicBoolean; + +public class ModemState +{ + private final Runnable onChanged; + private final AtomicBoolean changed = new AtomicBoolean( true ); + + private boolean open = false; + private final IntSet channels = new IntOpenHashSet(); + + public ModemState() + { + onChanged = null; + } + + public ModemState( Runnable onChanged ) + { + this.onChanged = onChanged; + } + + private void setOpen( boolean open ) + { + if( this.open == open ) return; + this.open = open; + if( !changed.getAndSet( true ) && onChanged != null ) onChanged.run(); + } + + public boolean pollChanged() + { + return changed.getAndSet( false ); + } + + public boolean isOpen() + { + return open; + } + + public boolean isOpen( int channel ) + { + synchronized( channels ) + { + return channels.contains( channel ); + } + } + + public void open( int channel ) throws LuaException + { + synchronized( channels ) + { + if( !channels.contains( channel ) ) + { + if( channels.size() >= 128 ) throw new LuaException( "Too many open channels" ); + channels.add( channel ); + setOpen( true ); + } + } + } + + public void close( int channel ) + { + synchronized( channels ) + { + channels.remove( channel ); + if( channels.isEmpty() ) setOpen( false ); + } + } + + public void closeAll() + { + synchronized( channels ) + { + channels.clear(); + setOpen( false ); + } + } +} diff --git a/remappedSrc/dan200/computercraft/shared/peripheral/modem/wired/BlockCable.java b/remappedSrc/dan200/computercraft/shared/peripheral/modem/wired/BlockCable.java new file mode 100644 index 000000000..414ab7e56 --- /dev/null +++ b/remappedSrc/dan200/computercraft/shared/peripheral/modem/wired/BlockCable.java @@ -0,0 +1,257 @@ +/* + * 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.shared.peripheral.modem.wired; + +import com.google.common.collect.ImmutableMap; +import dan200.computercraft.ComputerCraft; +import dan200.computercraft.api.ComputerCraftAPI; +import dan200.computercraft.shared.common.BlockGeneric; +import dan200.computercraft.shared.util.WaterloggableBlock; +import dan200.computercraft.shared.util.WorldUtil; +import net.fabricmc.api.EnvType; +import net.fabricmc.api.Environment; +import net.minecraft.block.Block; +import net.minecraft.block.BlockState; +import net.minecraft.block.ShapeContext; +import net.minecraft.block.entity.BlockEntity; +import net.minecraft.client.MinecraftClient; +import net.minecraft.entity.LivingEntity; +import net.minecraft.fluid.FluidState; +import net.minecraft.item.ItemPlacementContext; +import net.minecraft.item.ItemStack; +import net.minecraft.state.StateManager; +import net.minecraft.state.property.BooleanProperty; +import net.minecraft.state.property.EnumProperty; +import net.minecraft.util.hit.HitResult; +import net.minecraft.util.math.BlockPos; +import net.minecraft.util.math.Direction; +import net.minecraft.util.shape.VoxelShape; +import net.minecraft.world.BlockView; +import net.minecraft.world.CollisionView; +import net.minecraft.world.World; +import net.minecraft.world.WorldAccess; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; +import java.util.EnumMap; + +public class BlockCable extends BlockGeneric implements WaterloggableBlock +{ + public static final EnumProperty MODEM = EnumProperty.of( "modem", CableModemVariant.class ); + public static final BooleanProperty CABLE = BooleanProperty.of( "cable" ); + + private static final BooleanProperty NORTH = BooleanProperty.of( "north" ); + private static final BooleanProperty SOUTH = BooleanProperty.of( "south" ); + private static final BooleanProperty EAST = BooleanProperty.of( "east" ); + private static final BooleanProperty WEST = BooleanProperty.of( "west" ); + private static final BooleanProperty UP = BooleanProperty.of( "up" ); + private static final BooleanProperty DOWN = BooleanProperty.of( "down" ); + + static final EnumMap CONNECTIONS = + new EnumMap<>( new ImmutableMap.Builder() + .put( Direction.DOWN, DOWN ).put( Direction.UP, UP ) + .put( Direction.NORTH, NORTH ).put( Direction.SOUTH, SOUTH ) + .put( Direction.WEST, WEST ).put( Direction.EAST, EAST ) + .build() ); + + public BlockCable( Settings settings ) + { + super( settings, TileCable.FACTORY ); + + setDefaultState( getStateManager().getDefaultState() + .with( MODEM, CableModemVariant.None ) + .with( CABLE, false ) + .with( NORTH, false ).with( SOUTH, false ) + .with( EAST, false ).with( WEST, false ) + .with( UP, false ).with( DOWN, false ) + .with( WATERLOGGED, false ) + ); + } + + @Override + protected void appendProperties( StateManager.Builder builder ) + { + builder.add( MODEM, CABLE, NORTH, SOUTH, EAST, WEST, UP, DOWN, WATERLOGGED ); + } + + public static boolean canConnectIn( BlockState state, Direction direction ) + { + return state.get( BlockCable.CABLE ) && state.get( BlockCable.MODEM ).getFacing() != direction; + } + + public static boolean doesConnectVisually( BlockState state, BlockView world, BlockPos pos, Direction direction ) + { + if( !state.get( CABLE ) ) return false; + if( state.get( MODEM ).getFacing() == direction ) return true; + return ComputerCraftAPI.getWiredElementAt( world, pos.offset( direction ), direction.getOpposite() ) != null; + } + + @Override + @Deprecated + public VoxelShape getOutlineShape( BlockState state, BlockView world, BlockPos pos, ShapeContext position ) + { + return CableShapes.getShape( state ); + } + + /* + @Override + public boolean removedByPlayer( @Nonnull BlockState state, World world, @Nonnull BlockPos pos, @Nonnull PlayerEntity player, boolean willHarvest ) + { + PeripheralType type = getPeripheralType( world, pos ); + if( type == PeripheralType.WiredModemWithCable ) + { + RayTraceResult hit = state.collisionRayTrace( world, pos, WorldUtil.getRayStart( player ), WorldUtil.getRayEnd( player ) ); + if( hit != null ) + { + BlockEntity tile = world.getTileEntity( pos ); + if( tile != null && tile instanceof TileCable && tile.hasWorld() ) + { + TileCable cable = (TileCable) tile; + + ItemStack item; + + if( WorldUtil.isVecInside( CableShapes.getModemShape( state ), hit.hitVec.subtract( pos.getX(), pos.getY(), pos.getZ() ) ) ) + { + newState = state.with( MODEM, CableModemVariant.None ); + item = new ItemStack( ComputerCraft.Items.wiredModem ); + } + else + { + world.setBlockState( pos, state.with( CABLE, BlockCableCableVariant.NONE ), 3 ); + item = PeripheralItemFactory.create( PeripheralType.Cable, null, 1 ); + } + + cable.modemChanged(); + cable.connectionsChanged(); + if( !world.isRemote && !player.abilities.isCreativeMode ) + { + Block.spawnAsEntity( world, pos, item ); + } + + return false; + } + } + } + + return super.removedByPlayer( state, world, pos, player, willHarvest ); + } + */ + + @Nonnull + @Override + @Environment( EnvType.CLIENT ) + public ItemStack getPickStack( BlockView world, BlockPos pos, BlockState state ) + { + Direction modem = state.get( MODEM ).getFacing(); + boolean cable = state.get( CABLE ); + + // If we've only got one, just use that. + if( !cable ) return new ItemStack( ComputerCraft.Items.wiredModem ); + if( modem == null ) return new ItemStack( ComputerCraft.Items.cable ); + + // We've a modem and cable, so try to work out which one we're interacting with + HitResult hit = MinecraftClient.getInstance().crosshairTarget; + return hit != null && WorldUtil.isVecInside( CableShapes.getModemShape( state ), hit.getPos().subtract( pos.getX(), pos.getY(), pos.getZ() ) ) + ? new ItemStack( ComputerCraft.Items.wiredModem ) + : new ItemStack( ComputerCraft.Items.cable ); + + } + + @Override + public void onPlaced( World world, BlockPos pos, BlockState state, LivingEntity placer, ItemStack stack ) + { + BlockEntity tile = world.getBlockEntity( pos ); + if( tile instanceof TileCable ) + { + TileCable cable = (TileCable) tile; + if( cable.hasCable() ) cable.connectionsChanged(); + } + + super.onPlaced( world, pos, state, placer, stack ); + } + + @Nonnull + @Override + @Deprecated + public FluidState getFluidState( BlockState state ) + { + return getWaterloggedFluidState( state ); + } + + @Nonnull + @Override + @Deprecated + public BlockState getStateForNeighborUpdate( BlockState state, Direction side, BlockState otherState, WorldAccess world, BlockPos pos, BlockPos otherPos ) + { + updateWaterloggedPostPlacement( state, world, pos ); + + // Should never happen, but handle the case where we've no modem or cable. + if( !state.get( CABLE ) && state.get( MODEM ) == CableModemVariant.None ) + { + return getFluidState( state ).getBlockState(); + } + + return state.with( CONNECTIONS.get( side ), doesConnectVisually( state, world, pos, side ) ); + } + + @Override + @Deprecated + public boolean canPlaceAt( BlockState state, CollisionView world, BlockPos pos ) + { + Direction facing = state.get( MODEM ).getFacing(); + if( facing == null ) return true; + + BlockPos offsetPos = pos.offset( facing ); + BlockState offsetState = world.getBlockState( offsetPos ); + return Block.isFaceFullSquare( offsetState.getCollisionShape( world, offsetPos ), facing.getOpposite() ); + } + + @Nullable + @Override + public BlockState getPlacementState( ItemPlacementContext context ) + { + BlockState state = getDefaultState() + .with( WATERLOGGED, getWaterloggedStateForPlacement( context ) ); + + if( context.getStack().getItem() instanceof ItemBlockCable.Cable ) + { + World world = context.getWorld(); + BlockPos pos = context.getBlockPos(); + return correctConnections( world, pos, state.with( CABLE, true ) ); + } + else + { + return state.with( MODEM, CableModemVariant.from( context.getSide().getOpposite() ) ); + } + } + + public static BlockState correctConnections( World world, BlockPos pos, BlockState state ) + { + if( state.get( CABLE ) ) + { + return state + .with( NORTH, doesConnectVisually( state, world, pos, Direction.NORTH ) ) + .with( SOUTH, doesConnectVisually( state, world, pos, Direction.SOUTH ) ) + .with( EAST, doesConnectVisually( state, world, pos, Direction.EAST ) ) + .with( WEST, doesConnectVisually( state, world, pos, Direction.WEST ) ) + .with( UP, doesConnectVisually( state, world, pos, Direction.UP ) ) + .with( DOWN, doesConnectVisually( state, world, pos, Direction.DOWN ) ); + } + else + { + return state + .with( NORTH, false ).with( SOUTH, false ).with( EAST, false ) + .with( WEST, false ).with( UP, false ).with( DOWN, false ); + } + } + + @Override + @Deprecated + public boolean hasBlockEntityBreakingRender( BlockState state ) + { + return true; + } +} diff --git a/remappedSrc/dan200/computercraft/shared/peripheral/modem/wired/BlockWiredModemFull.java b/remappedSrc/dan200/computercraft/shared/peripheral/modem/wired/BlockWiredModemFull.java new file mode 100644 index 000000000..2c893e47b --- /dev/null +++ b/remappedSrc/dan200/computercraft/shared/peripheral/modem/wired/BlockWiredModemFull.java @@ -0,0 +1,34 @@ +/* + * 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.shared.peripheral.modem.wired; + +import dan200.computercraft.shared.common.BlockGeneric; +import net.minecraft.block.Block; +import net.minecraft.block.BlockState; +import net.minecraft.state.StateManager; +import net.minecraft.state.property.BooleanProperty; + +public class BlockWiredModemFull extends BlockGeneric +{ + public static final BooleanProperty MODEM_ON = BooleanProperty.of( "modem" ); + public static final BooleanProperty PERIPHERAL_ON = BooleanProperty.of( "peripheral" ); + + public BlockWiredModemFull( Settings settings ) + { + super( settings, TileWiredModemFull.FACTORY ); + setDefaultState( getStateManager().getDefaultState() + .with( MODEM_ON, false ) + .with( PERIPHERAL_ON, false ) + ); + } + + @Override + protected void appendProperties( StateManager.Builder builder ) + { + builder.add( MODEM_ON, PERIPHERAL_ON ); + } +} diff --git a/remappedSrc/dan200/computercraft/shared/peripheral/modem/wired/CableModemVariant.java b/remappedSrc/dan200/computercraft/shared/peripheral/modem/wired/CableModemVariant.java new file mode 100644 index 000000000..70f9eab22 --- /dev/null +++ b/remappedSrc/dan200/computercraft/shared/peripheral/modem/wired/CableModemVariant.java @@ -0,0 +1,83 @@ +/* + * 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.shared.peripheral.modem.wired; + +import net.minecraft.util.StringIdentifiable; +import net.minecraft.util.math.Direction; + +import javax.annotation.Nonnull; + +public enum CableModemVariant implements StringIdentifiable +{ + None( "none", null ), + DownOff( "down_off", Direction.DOWN ), + UpOff( "up_off", Direction.UP ), + NorthOff( "north_off", Direction.NORTH ), + SouthOff( "south_off", Direction.SOUTH ), + WestOff( "west_off", Direction.WEST ), + EastOff( "east_off", Direction.EAST ), + DownOn( "down_on", Direction.DOWN ), + UpOn( "up_on", Direction.UP ), + NorthOn( "north_on", Direction.NORTH ), + SouthOn( "south_on", Direction.SOUTH ), + WestOn( "west_on", Direction.WEST ), + EastOn( "east_on", Direction.EAST ), + DownOffPeripheral( "down_off_peripheral", Direction.DOWN ), + UpOffPeripheral( "up_off_peripheral", Direction.UP ), + NorthOffPeripheral( "north_off_peripheral", Direction.NORTH ), + SouthOffPeripheral( "south_off_peripheral", Direction.SOUTH ), + WestOffPeripheral( "west_off_peripheral", Direction.WEST ), + EastOffPeripheral( "east_off_peripheral", Direction.EAST ), + DownOnPeripheral( "down_on_peripheral", Direction.DOWN ), + UpOnPeripheral( "up_on_peripheral", Direction.UP ), + NorthOnPeripheral( "north_on_peripheral", Direction.NORTH ), + SouthOnPeripheral( "south_on_peripheral", Direction.SOUTH ), + WestOnPeripheral( "west_on_peripheral", Direction.WEST ), + EastOnPeripheral( "east_on_peripheral", Direction.EAST ); + + private static final CableModemVariant[] VALUES = values(); + + private final String name; + private final Direction facing; + + CableModemVariant( String name, Direction facing ) + { + this.name = name; + this.facing = facing; + } + + @Nonnull + public static CableModemVariant from( Direction facing ) + { + return facing == null ? None : VALUES[1 + facing.getId()]; + } + + @Nonnull + public static CableModemVariant from( Direction facing, boolean modem, boolean peripheral ) + { + int state = (modem ? 2 : 0) + (peripheral ? 1 : 0); + return facing == null ? None : VALUES[1 + 6 * state + facing.getId()]; + } + + @Nonnull + @Override + public String asString() + { + return name; + } + + public Direction getFacing() + { + return facing; + } + + @Override + public String toString() + { + return name; + } +} diff --git a/remappedSrc/dan200/computercraft/shared/peripheral/modem/wired/CableShapes.java b/remappedSrc/dan200/computercraft/shared/peripheral/modem/wired/CableShapes.java new file mode 100644 index 000000000..fc26e8694 --- /dev/null +++ b/remappedSrc/dan200/computercraft/shared/peripheral/modem/wired/CableShapes.java @@ -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.shared.peripheral.modem.wired; + +import com.google.common.collect.ImmutableMap; +import dan200.computercraft.shared.peripheral.modem.ModemShapes; +import dan200.computercraft.shared.util.DirectionUtil; +import net.minecraft.block.BlockState; +import net.minecraft.util.math.Direction; +import net.minecraft.util.shape.VoxelShape; +import net.minecraft.util.shape.VoxelShapes; + +import java.util.EnumMap; + +import static dan200.computercraft.shared.peripheral.modem.wired.BlockCable.*; + +public final class CableShapes +{ + private static final double MIN = 0.375; + private static final double MAX = 1 - MIN; + + private static final VoxelShape SHAPE_CABLE_CORE = VoxelShapes.cuboid( MIN, MIN, MIN, MAX, MAX, MAX ); + private static final EnumMap SHAPE_CABLE_ARM = + new EnumMap<>( new ImmutableMap.Builder() + .put( Direction.DOWN, VoxelShapes.cuboid( MIN, 0, MIN, MAX, MIN, MAX ) ) + .put( Direction.UP, VoxelShapes.cuboid( MIN, MAX, MIN, MAX, 1, MAX ) ) + .put( Direction.NORTH, VoxelShapes.cuboid( MIN, MIN, 0, MAX, MAX, MIN ) ) + .put( Direction.SOUTH, VoxelShapes.cuboid( MIN, MIN, MAX, MAX, MAX, 1 ) ) + .put( Direction.WEST, VoxelShapes.cuboid( 0, MIN, MIN, MIN, MAX, MAX ) ) + .put( Direction.EAST, VoxelShapes.cuboid( MAX, MIN, MIN, 1, MAX, MAX ) ) + .build() + ); + + private static final VoxelShape[] SHAPES = new VoxelShape[(1 << 6) * 7]; + private static final VoxelShape[] CABLE_SHAPES = new VoxelShape[1 << 6]; + + private CableShapes() + { + } + + private static int getCableIndex( BlockState state ) + { + int index = 0; + for( Direction facing : DirectionUtil.FACINGS ) + { + if( state.get( CONNECTIONS.get( facing ) ) ) index |= 1 << facing.ordinal(); + } + + return index; + } + + private static VoxelShape getCableShape( int index ) + { + VoxelShape shape = CABLE_SHAPES[index]; + if( shape != null ) return shape; + + shape = SHAPE_CABLE_CORE; + for( Direction facing : DirectionUtil.FACINGS ) + { + if( (index & (1 << facing.ordinal())) != 0 ) + { + shape = VoxelShapes.union( shape, SHAPE_CABLE_ARM.get( facing ) ); + } + } + + return CABLE_SHAPES[index] = shape; + } + + public static VoxelShape getCableShape( BlockState state ) + { + if( !state.get( CABLE ) ) return VoxelShapes.empty(); + return getCableShape( getCableIndex( state ) ); + } + + public static VoxelShape getModemShape( BlockState state ) + { + Direction facing = state.get( MODEM ).getFacing(); + return facing == null ? VoxelShapes.empty() : ModemShapes.getBounds( facing ); + } + + public static VoxelShape getShape( BlockState state ) + { + Direction facing = state.get( MODEM ).getFacing(); + if( !state.get( CABLE ) ) return getModemShape( state ); + + int cableIndex = getCableIndex( state ); + int index = cableIndex + ((facing == null ? 0 : facing.ordinal() + 1) << 6); + + VoxelShape shape = SHAPES[index]; + if( shape != null ) return shape; + + shape = getCableShape( cableIndex ); + if( facing != null ) shape = VoxelShapes.union( shape, ModemShapes.getBounds( facing ) ); + return SHAPES[index] = shape; + } +} diff --git a/remappedSrc/dan200/computercraft/shared/peripheral/modem/wired/ItemBlockCable.java b/remappedSrc/dan200/computercraft/shared/peripheral/modem/wired/ItemBlockCable.java new file mode 100644 index 000000000..56ccd5a01 --- /dev/null +++ b/remappedSrc/dan200/computercraft/shared/peripheral/modem/wired/ItemBlockCable.java @@ -0,0 +1,151 @@ +/* + * 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.shared.peripheral.modem.wired; + +import dan200.computercraft.ComputerCraft; +import net.minecraft.block.BlockState; +import net.minecraft.block.entity.BlockEntity; +import net.minecraft.item.*; +import net.minecraft.sound.BlockSoundGroup; +import net.minecraft.sound.SoundCategory; +import net.minecraft.util.ActionResult; +import net.minecraft.util.Util; +import net.minecraft.util.collection.DefaultedList; +import net.minecraft.util.math.BlockPos; +import net.minecraft.util.math.Direction; +import net.minecraft.util.registry.Registry; +import net.minecraft.world.World; + +import javax.annotation.Nonnull; + +import static dan200.computercraft.shared.peripheral.modem.wired.BlockCable.*; + +public abstract class ItemBlockCable extends BlockItem +{ + private String translationKey; + + public ItemBlockCable( BlockCable block, Item.Settings settings ) + { + super( block, settings ); + } + + boolean placeAt( World world, BlockPos pos, BlockState state ) + { + if( !state.canPlaceAt( world, pos ) ) return false; + + world.setBlockState( pos, state, 3 ); + BlockSoundGroup soundType = state.getBlock().getSoundGroup( state ); + world.playSound( null, pos, soundType.getPlaceSound(), SoundCategory.BLOCKS, (soundType.getVolume() + 1.0F) / 2.0F, soundType.getPitch() * 0.8F ); + + BlockEntity tile = world.getBlockEntity( pos ); + if( tile instanceof TileCable ) + { + TileCable cable = (TileCable) tile; + cable.modemChanged(); + cable.connectionsChanged(); + } + + return true; + } + + boolean placeAtCorrected( World world, BlockPos pos, BlockState state ) + { + return placeAt( world, pos, correctConnections( world, pos, state ) ); + } + + @Override + public void appendStacks( ItemGroup group, DefaultedList list ) + { + if( isIn( group ) ) list.add( new ItemStack( this ) ); + } + + @Override + public String getTranslationKey() + { + if( translationKey == null ) + { + translationKey = Util.createTranslationKey( "block", Registry.ITEM.getId( this ) ); + } + return translationKey; + } + + public static class WiredModem extends ItemBlockCable + { + public WiredModem( BlockCable block, Settings settings ) + { + super( block, settings ); + } + + @Nonnull + @Override + public ActionResult place( ItemPlacementContext context ) + { + ItemStack stack = context.getStack(); + if( stack.isEmpty() ) return ActionResult.FAIL; + + World world = context.getWorld(); + BlockPos pos = context.getBlockPos(); + BlockState existingState = world.getBlockState( pos ); + + // Try to add a modem to a cable + if( existingState.getBlock() == ComputerCraft.Blocks.cable && existingState.get( MODEM ) == CableModemVariant.None ) + { + Direction side = context.getSide().getOpposite(); + BlockState newState = existingState + .with( MODEM, CableModemVariant.from( side ) ) + .with( CONNECTIONS.get( side ), existingState.get( CABLE ) ); + if( placeAt( world, pos, newState ) ) + { + stack.decrement( 1 ); + return ActionResult.SUCCESS; + } + } + + return super.place( context ); + } + } + + public static class Cable extends ItemBlockCable + { + public Cable( BlockCable block, Settings settings ) + { + super( block, settings ); + } + + @Nonnull + @Override + public ActionResult place( ItemPlacementContext context ) + { + ItemStack stack = context.getStack(); + if( stack.isEmpty() ) return ActionResult.FAIL; + + World world = context.getWorld(); + BlockPos pos = context.getBlockPos(); + + // Try to add a cable to a modem inside the block we're clicking on. + BlockPos insidePos = pos.offset( context.getSide().getOpposite() ); + BlockState insideState = world.getBlockState( insidePos ); + if( insideState.getBlock() == ComputerCraft.Blocks.cable && !insideState.get( BlockCable.CABLE ) + && placeAtCorrected( world, insidePos, insideState.with( BlockCable.CABLE, true ) ) ) + { + stack.decrement( 1 ); + return ActionResult.SUCCESS; + } + + // Try to add a cable to a modem adjacent to this block + BlockState existingState = world.getBlockState( pos ); + if( existingState.getBlock() == ComputerCraft.Blocks.cable && !existingState.get( BlockCable.CABLE ) + && placeAtCorrected( world, pos, existingState.with( BlockCable.CABLE, true ) ) ) + { + stack.decrement( 1 ); + return ActionResult.SUCCESS; + } + + return super.place( context ); + } + } +} diff --git a/remappedSrc/dan200/computercraft/shared/peripheral/modem/wired/TileCable.java b/remappedSrc/dan200/computercraft/shared/peripheral/modem/wired/TileCable.java new file mode 100644 index 000000000..461fc6551 --- /dev/null +++ b/remappedSrc/dan200/computercraft/shared/peripheral/modem/wired/TileCable.java @@ -0,0 +1,418 @@ +/* + * 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.shared.peripheral.modem.wired; + +import com.google.common.base.Objects; +import dan200.computercraft.ComputerCraft; +import dan200.computercraft.api.ComputerCraftAPI; +import dan200.computercraft.api.network.wired.IWiredElement; +import dan200.computercraft.api.network.wired.IWiredNode; +import dan200.computercraft.api.peripheral.IPeripheral; +import dan200.computercraft.api.peripheral.IPeripheralTile; +import dan200.computercraft.shared.command.CommandCopy; +import dan200.computercraft.shared.common.TileGeneric; +import dan200.computercraft.shared.peripheral.modem.ModemState; +import dan200.computercraft.shared.util.DirectionUtil; +import dan200.computercraft.shared.util.NamedBlockEntityType; +import dan200.computercraft.shared.util.TickScheduler; +import net.minecraft.block.Block; +import net.minecraft.block.BlockState; +import net.minecraft.entity.player.PlayerEntity; +import net.minecraft.item.ItemStack; +import net.minecraft.nbt.CompoundTag; +import net.minecraft.text.TranslatableText; +import net.minecraft.util.Hand; +import net.minecraft.util.Identifier; +import net.minecraft.util.hit.BlockHitResult; +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 java.util.Collections; +import java.util.Map; + +public class TileCable extends TileGeneric implements IPeripheralTile +{ + public static final NamedBlockEntityType FACTORY = NamedBlockEntityType.create( + new Identifier( ComputerCraft.MOD_ID, "cable" ), + TileCable::new + ); + + private static final String NBT_PERIPHERAL_ENABLED = "PeirpheralAccess"; + + private class CableElement extends WiredModemElement + { + @Nonnull + @Override + public World getWorld() + { + return TileCable.this.getWorld(); + } + + @Nonnull + @Override + public Vec3d getPosition() + { + BlockPos pos = getPos(); + return new Vec3d( pos.getX() + 0.5, pos.getY() + 0.5, pos.getZ() + 0.5 ); + } + + @Override + protected void attachPeripheral( String name, IPeripheral peripheral ) + { + m_modem.attachPeripheral( name, peripheral ); + } + + @Override + protected void detachPeripheral( String name ) + { + m_modem.detachPeripheral( name ); + } + } + + private boolean m_peripheralAccessAllowed; + private WiredModemLocalPeripheral m_peripheral = new WiredModemLocalPeripheral(); + + private boolean m_destroyed = false; + + private Direction modemDirection = Direction.NORTH; + private boolean hasModemDirection = false; + private boolean m_connectionsFormed = false; + + private final WiredModemElement m_cable = new CableElement(); + private final IWiredNode m_node = m_cable.getNode(); + private final WiredModemPeripheral m_modem = new WiredModemPeripheral( + new ModemState( () -> TickScheduler.schedule( this ) ), + m_cable + ) + { + @Nonnull + @Override + protected WiredModemLocalPeripheral getLocalPeripheral() + { + return m_peripheral; + } + + @Nonnull + @Override + public Vec3d getPosition() + { + BlockPos pos = getPos().offset( modemDirection ); + return new Vec3d( pos.getX() + 0.5, pos.getY() + 0.5, pos.getZ() + 0.5 ); + } + }; + + public TileCable() + { + super( FACTORY ); + } + + private void onRemove() + { + if( world == null || !world.isClient ) + { + m_node.remove(); + m_connectionsFormed = false; + } + } + + @Override + public void destroy() + { + if( !m_destroyed ) + { + m_destroyed = true; + m_modem.destroy(); + onRemove(); + } + } + + /* + @Override + public void onChunkUnload() + { + super.onChunkUnload(); + remove(); + } + */ + + @Override + public void markRemoved() + { + super.markRemoved(); + onRemove(); + } + + @Override + public void cancelRemoval() + { + super.cancelRemoval(); + TickScheduler.schedule( this ); + } + + @Override + public void resetBlock() + { + super.resetBlock(); + hasModemDirection = false; + if( !world.isClient ) world.getBlockTickScheduler().schedule( pos, getCachedState().getBlock(), 0 ); + } + + private void updateDirection() + { + if( !hasModemDirection ) + { + hasModemDirection = true; + modemDirection = getDirection(); + } + } + + public Direction getDirection() + { + BlockState state = getCachedState(); + Direction facing = state.get( BlockCable.MODEM ).getFacing(); + return facing != null ? facing : Direction.NORTH; + } + + @Override + public void onNeighbourChange( @Nonnull BlockPos neighbour ) + { + Direction dir = getDirection(); + if( neighbour.equals( getPos().offset( dir ) ) && hasModem() + && !getCachedState().canPlaceAt( world, getPos() ) + ) + { + if( hasCable() ) + { + // Drop the modem and convert to cable + Block.dropStack( getWorld(), getPos(), new ItemStack( ComputerCraft.Items.wiredModem ) ); + getWorld().setBlockState( getPos(), getCachedState().with( BlockCable.MODEM, CableModemVariant.None ) ); + modemChanged(); + connectionsChanged(); + } + else + { + // Drop everything and remove block + Block.dropStack( getWorld(), getPos(), new ItemStack( ComputerCraft.Items.wiredModem ) ); + getWorld().removeBlock( getPos(), false ); + // This'll call #destroy(), so we don't need to reset the network here. + } + + return; + } + + onNeighbourTileEntityChange( neighbour ); + } + + @Override + public void onNeighbourTileEntityChange( @Nonnull BlockPos neighbour ) + { + super.onNeighbourTileEntityChange( neighbour ); + if( !world.isClient && m_peripheralAccessAllowed ) + { + Direction facing = getDirection(); + if( getPos().offset( facing ).equals( neighbour ) ) + { + if( m_peripheral.attach( world, getPos(), facing ) ) updateConnectedPeripherals(); + } + } + } + + @Override + public boolean onActivate( PlayerEntity player, Hand hand, BlockHitResult hit ) + { + if( !canAttachPeripheral() || player.isSneaking() ) return false; + + if( getWorld().isClient ) return true; + + String oldName = m_peripheral.getConnectedName(); + togglePeripheralAccess(); + String newName = m_peripheral.getConnectedName(); + if( !Objects.equal( newName, oldName ) ) + { + if( oldName != null ) + { + player.sendMessage( new TranslatableText( "chat.computercraft.wired_modem.peripheral_disconnected", + CommandCopy.createCopyText( oldName ) ), false ); + } + if( newName != null ) + { + player.sendMessage( new TranslatableText( "chat.computercraft.wired_modem.peripheral_connected", + CommandCopy.createCopyText( newName ) ), false ); + } + } + + return true; + } + + @Override + public void fromTag( CompoundTag nbt ) + { + super.fromTag( nbt ); + m_peripheralAccessAllowed = nbt.getBoolean( NBT_PERIPHERAL_ENABLED ); + m_peripheral.fromTag( nbt, "" ); + } + + @Nonnull + @Override + public CompoundTag toTag( CompoundTag nbt ) + { + nbt.putBoolean( NBT_PERIPHERAL_ENABLED, m_peripheralAccessAllowed ); + m_peripheral.toTag( nbt, "" ); + return super.toTag( nbt ); + } + + private void updateBlockState() + { + BlockState state = getCachedState(); + CableModemVariant oldVariant = state.get( BlockCable.MODEM ); + CableModemVariant newVariant = CableModemVariant + .from( oldVariant.getFacing(), m_modem.getModemState().isOpen(), m_peripheralAccessAllowed ); + + if( oldVariant != newVariant ) + { + world.setBlockState( getPos(), state.with( BlockCable.MODEM, newVariant ) ); + } + } + + @Override + public void blockTick() + { + if( getWorld().isClient ) return; + + updateDirection(); + + if( m_modem.getModemState().pollChanged() ) updateBlockState(); + + if( !m_connectionsFormed ) + { + m_connectionsFormed = true; + + connectionsChanged(); + if( m_peripheralAccessAllowed ) + { + m_peripheral.attach( world, pos, modemDirection ); + updateConnectedPeripherals(); + } + } + } + + void connectionsChanged() + { + if( getWorld().isClient ) return; + + BlockState state = getCachedState(); + World world = getWorld(); + BlockPos current = getPos(); + for( Direction facing : DirectionUtil.FACINGS ) + { + BlockPos offset = current.offset( facing ); + if( !world.isBlockLoaded( offset ) ) continue; + + IWiredElement element = ComputerCraftAPI.getWiredElementAt( world, offset, facing.getOpposite() ); + if( element == null ) continue; + + IWiredNode node = element.getNode(); + if( BlockCable.canConnectIn( state, facing ) ) + { + // If we can connect to it then do so + m_node.connectTo( node ); + } + else if( m_node.getNetwork() == node.getNetwork() ) + { + // Otherwise if we're on the same network then attempt to void it. + m_node.disconnectFrom( node ); + } + } + } + + void modemChanged() + { + if( getWorld().isClient ) return; + + // If we can no longer attach peripherals, then detach any + // which may have existed + if( !canAttachPeripheral() && m_peripheralAccessAllowed ) + { + m_peripheralAccessAllowed = false; + m_peripheral.detach(); + m_node.updatePeripherals( Collections.emptyMap() ); + markDirty(); + updateBlockState(); + } + } + + private void togglePeripheralAccess() + { + if( !m_peripheralAccessAllowed ) + { + m_peripheral.attach( world, getPos(), getDirection() ); + if( !m_peripheral.hasPeripheral() ) return; + + m_peripheralAccessAllowed = true; + m_node.updatePeripherals( m_peripheral.toMap() ); + } + else + { + m_peripheral.detach(); + + m_peripheralAccessAllowed = false; + m_node.updatePeripherals( Collections.emptyMap() ); + } + + updateBlockState(); + } + + private void updateConnectedPeripherals() + { + Map peripherals = m_peripheral.toMap(); + if( peripherals.isEmpty() ) + { + // If there are no peripherals then disable access and update the display state. + m_peripheralAccessAllowed = false; + updateBlockState(); + } + + m_node.updatePeripherals( peripherals ); + } + + /* + @Override + public boolean canRenderBreaking() + { + return true; + } + */ + + public IWiredElement getElement( Direction facing ) + { + return BlockCable.canConnectIn( getCachedState(), facing ) ? m_cable : null; + } + + @Override + public IPeripheral getPeripheral( @Nonnull Direction side ) + { + return !m_destroyed && hasModem() && side == getDirection() ? m_modem : null; + } + + boolean hasCable() + { + return getCachedState().get( BlockCable.CABLE ); + } + + public boolean hasModem() + { + return getCachedState().get( BlockCable.MODEM ) != CableModemVariant.None; + } + + private boolean canAttachPeripheral() + { + return hasCable() && hasModem(); + } +} diff --git a/remappedSrc/dan200/computercraft/shared/peripheral/modem/wired/TileWiredModemFull.java b/remappedSrc/dan200/computercraft/shared/peripheral/modem/wired/TileWiredModemFull.java new file mode 100644 index 000000000..facdeef32 --- /dev/null +++ b/remappedSrc/dan200/computercraft/shared/peripheral/modem/wired/TileWiredModemFull.java @@ -0,0 +1,394 @@ +/* + * 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.shared.peripheral.modem.wired; + +import com.google.common.base.Objects; +import dan200.computercraft.ComputerCraft; +import dan200.computercraft.api.ComputerCraftAPI; +import dan200.computercraft.api.network.wired.IWiredElement; +import dan200.computercraft.api.network.wired.IWiredNode; +import dan200.computercraft.api.peripheral.IPeripheral; +import dan200.computercraft.api.peripheral.IPeripheralTile; +import dan200.computercraft.shared.command.CommandCopy; +import dan200.computercraft.shared.common.TileGeneric; +import dan200.computercraft.shared.peripheral.modem.ModemState; +import dan200.computercraft.shared.util.DirectionUtil; +import dan200.computercraft.shared.util.NamedBlockEntityType; +import dan200.computercraft.shared.util.TickScheduler; +import net.minecraft.block.BlockState; +import net.minecraft.entity.player.PlayerEntity; +import net.minecraft.nbt.CompoundTag; +import net.minecraft.text.LiteralText; +import net.minecraft.text.TranslatableText; +import net.minecraft.util.Hand; +import net.minecraft.util.Identifier; +import net.minecraft.util.hit.BlockHitResult; +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 java.util.*; + +import static dan200.computercraft.shared.peripheral.modem.wired.BlockWiredModemFull.MODEM_ON; +import static dan200.computercraft.shared.peripheral.modem.wired.BlockWiredModemFull.PERIPHERAL_ON; + +public class TileWiredModemFull extends TileGeneric implements IPeripheralTile +{ + public static final NamedBlockEntityType FACTORY = NamedBlockEntityType.create( + new Identifier( ComputerCraft.MOD_ID, "wired_modem_full" ), + TileWiredModemFull::new + ); + + private static final String NBT_PERIPHERAL_ENABLED = "PeripheralAccess"; + + private static final class FullElement extends WiredModemElement + { + private final TileWiredModemFull m_entity; + + private FullElement( TileWiredModemFull entity ) + { + m_entity = entity; + } + + @Override + protected void attachPeripheral( String name, IPeripheral peripheral ) + { + for( int i = 0; i < 6; i++ ) + { + WiredModemPeripheral modem = m_entity.m_modems[i]; + if( modem != null ) modem.attachPeripheral( name, peripheral ); + } + } + + @Override + protected void detachPeripheral( String name ) + { + for( int i = 0; i < 6; i++ ) + { + WiredModemPeripheral modem = m_entity.m_modems[i]; + if( modem != null ) modem.detachPeripheral( name ); + } + } + + @Nonnull + @Override + public World getWorld() + { + return m_entity.getWorld(); + } + + @Nonnull + @Override + public Vec3d getPosition() + { + BlockPos pos = m_entity.getPos(); + return new Vec3d( pos.getX() + 0.5, pos.getY() + 0.5, pos.getZ() + 0.5 ); + } + } + + private WiredModemPeripheral[] m_modems = new WiredModemPeripheral[6]; + + private boolean m_peripheralAccessAllowed = false; + private WiredModemLocalPeripheral[] m_peripherals = new WiredModemLocalPeripheral[6]; + + private boolean m_destroyed = false; + private boolean m_connectionsFormed = false; + + private final ModemState m_modemState = new ModemState( () -> TickScheduler.schedule( this ) ); + private final WiredModemElement m_element = new FullElement( this ); + private final IWiredNode m_node = m_element.getNode(); + + public TileWiredModemFull() + { + super( FACTORY ); + for( int i = 0; i < m_peripherals.length; i++ ) m_peripherals[i] = new WiredModemLocalPeripheral(); + } + + private void doRemove() + { + if( world == null || !world.isClient ) + { + m_node.remove(); + m_connectionsFormed = false; + } + } + + @Override + public void destroy() + { + if( !m_destroyed ) + { + m_destroyed = true; + doRemove(); + } + super.destroy(); + } + + /* + @Override + public void onChunkUnloaded() + { + super.onChunkUnloaded(); + doRemove(); + } + */ + + @Override + public void markRemoved() + { + super.markRemoved(); + doRemove(); + } + + @Override + public void onNeighbourChange( @Nonnull BlockPos neighbour ) + { + onNeighbourTileEntityChange( neighbour ); + } + + @Override + public void onNeighbourTileEntityChange( @Nonnull BlockPos neighbour ) + { + if( !world.isClient && m_peripheralAccessAllowed ) + { + for( Direction facing : DirectionUtil.FACINGS ) + { + if( getPos().offset( facing ).equals( neighbour ) ) + { + WiredModemLocalPeripheral peripheral = m_peripherals[facing.ordinal()]; + if( peripheral.attach( world, getPos(), facing ) ) updateConnectedPeripherals(); + } + } + } + } + + @Override + public boolean onActivate( PlayerEntity player, Hand hand, BlockHitResult hit ) + { + if( getWorld().isClient ) return true; + + // On server, we interacted if a peripheral was found + Set oldPeriphNames = getConnectedPeripheralNames(); + togglePeripheralAccess(); + Set periphNames = getConnectedPeripheralNames(); + + if( !Objects.equal( periphNames, oldPeriphNames ) ) + { + sendPeripheralChanges( player, "chat.computercraft.wired_modem.peripheral_disconnected", oldPeriphNames ); + sendPeripheralChanges( player, "chat.computercraft.wired_modem.peripheral_connected", periphNames ); + } + + return true; + } + + private static void sendPeripheralChanges( PlayerEntity player, String kind, Collection peripherals ) + { + if( peripherals.isEmpty() ) return; + + List names = new ArrayList<>( peripherals ); + names.sort( Comparator.naturalOrder() ); + + LiteralText base = new LiteralText( "" ); + for( int i = 0; i < names.size(); i++ ) + { + if( i > 0 ) base.append( ", " ); + base.append( CommandCopy.createCopyText( names.get( i ) ) ); + } + + player.sendMessage( new TranslatableText( kind, base ), false ); + } + + @Override + public void fromTag( CompoundTag nbt ) + { + super.fromTag( nbt ); + m_peripheralAccessAllowed = nbt.getBoolean( NBT_PERIPHERAL_ENABLED ); + for( int i = 0; i < m_peripherals.length; i++ ) m_peripherals[i].fromTag( nbt, Integer.toString( i ) ); + } + + @Nonnull + @Override + public CompoundTag toTag( CompoundTag nbt ) + { + nbt.putBoolean( NBT_PERIPHERAL_ENABLED, m_peripheralAccessAllowed ); + for( int i = 0; i < m_peripherals.length; i++ ) m_peripherals[i].toTag( nbt, Integer.toString( i ) ); + return super.toTag( nbt ); + } + + private void updateBlockState() + { + BlockState state = getCachedState(); + boolean modemOn = m_modemState.isOpen(), peripheralOn = m_peripheralAccessAllowed; + if( state.get( MODEM_ON ) == modemOn && state.get( PERIPHERAL_ON ) == peripheralOn ) return; + + getWorld().setBlockState( getPos(), state.with( MODEM_ON, modemOn ).with( PERIPHERAL_ON, peripheralOn ) ); + } + + @Override + public void cancelRemoval() + { + super.cancelRemoval(); + TickScheduler.schedule( this ); + } + + @Override + public void blockTick() + { + if( getWorld().isClient ) return; + + if( m_modemState.pollChanged() ) updateBlockState(); + + if( !m_connectionsFormed ) + { + m_connectionsFormed = true; + + connectionsChanged(); + if( m_peripheralAccessAllowed ) + { + for( Direction facing : DirectionUtil.FACINGS ) + { + m_peripherals[facing.ordinal()].attach( world, getPos(), facing ); + } + updateConnectedPeripherals(); + } + } + } + + private void connectionsChanged() + { + if( getWorld().isClient ) return; + + World world = getWorld(); + BlockPos current = getPos(); + for( Direction facing : DirectionUtil.FACINGS ) + { + BlockPos offset = current.offset( facing ); + if( !world.isBlockLoaded( offset ) ) continue; + + IWiredElement element = ComputerCraftAPI.getWiredElementAt( world, offset, facing.getOpposite() ); + if( element == null ) continue; + + m_node.connectTo( element.getNode() ); + } + } + + private void togglePeripheralAccess() + { + if( !m_peripheralAccessAllowed ) + { + boolean hasAny = false; + for( Direction facing : DirectionUtil.FACINGS ) + { + WiredModemLocalPeripheral peripheral = m_peripherals[facing.ordinal()]; + peripheral.attach( world, getPos(), facing ); + hasAny |= peripheral.hasPeripheral(); + } + + if( !hasAny ) return; + + m_peripheralAccessAllowed = true; + m_node.updatePeripherals( getConnectedPeripherals() ); + } + else + { + m_peripheralAccessAllowed = false; + + for( WiredModemLocalPeripheral peripheral : m_peripherals ) peripheral.detach(); + m_node.updatePeripherals( Collections.emptyMap() ); + } + + updateBlockState(); + } + + private Set getConnectedPeripheralNames() + { + if( !m_peripheralAccessAllowed ) return Collections.emptySet(); + + Set peripherals = new HashSet<>( 6 ); + for( WiredModemLocalPeripheral peripheral : m_peripherals ) + { + String name = peripheral.getConnectedName(); + if( name != null ) peripherals.add( name ); + } + return peripherals; + } + + private Map getConnectedPeripherals() + { + if( !m_peripheralAccessAllowed ) return Collections.emptyMap(); + + Map peripherals = new HashMap<>( 6 ); + for( WiredModemLocalPeripheral peripheral : m_peripherals ) peripheral.extendMap( peripherals ); + return peripherals; + } + + private void updateConnectedPeripherals() + { + Map peripherals = getConnectedPeripherals(); + if( peripherals.isEmpty() ) + { + // If there are no peripherals then disable access and update the display state. + m_peripheralAccessAllowed = false; + updateBlockState(); + } + + m_node.updatePeripherals( peripherals ); + } + + /* + @Nonnull + @Override + public LazyOptional getCapability( @Nonnull Capability capability, @Nullable Direction facing ) + { + if( capability == CapabilityWiredElement.CAPABILITY ) + { + if( elementCap == null ) elementCap = LazyOptional.of( () -> m_element ); + return elementCap.cast(); + } + return super.getCapability( capability, facing ); + } + */ + + public IWiredElement getElement() + { + return m_element; + } + + // IPeripheralTile + + @Override + public IPeripheral getPeripheral( @Nonnull Direction side ) + { + if( m_destroyed ) return null; + + WiredModemPeripheral peripheral = m_modems[side.ordinal()]; + if( peripheral == null ) + { + WiredModemLocalPeripheral localPeripheral = m_peripherals[side.ordinal()]; + peripheral = m_modems[side.ordinal()] = new WiredModemPeripheral( m_modemState, m_element ) + { + @Nonnull + @Override + protected WiredModemLocalPeripheral getLocalPeripheral() + { + return localPeripheral; + } + + @Nonnull + @Override + public Vec3d getPosition() + { + BlockPos pos = getPos().offset( side ); + return new Vec3d( pos.getX() + 0.5, pos.getY() + 0.5, pos.getZ() + 0.5 ); + } + }; + } + return peripheral; + } +} diff --git a/remappedSrc/dan200/computercraft/shared/peripheral/modem/wired/WiredModemElement.java b/remappedSrc/dan200/computercraft/shared/peripheral/modem/wired/WiredModemElement.java new file mode 100644 index 000000000..545c7bda2 --- /dev/null +++ b/remappedSrc/dan200/computercraft/shared/peripheral/modem/wired/WiredModemElement.java @@ -0,0 +1,65 @@ +/* + * 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.shared.peripheral.modem.wired; + +import dan200.computercraft.api.network.wired.IWiredElement; +import dan200.computercraft.api.network.wired.IWiredNetworkChange; +import dan200.computercraft.api.network.wired.IWiredNode; +import dan200.computercraft.api.peripheral.IPeripheral; +import dan200.computercraft.shared.wired.WiredNode; + +import javax.annotation.Nonnull; +import java.util.HashMap; +import java.util.Map; + +public abstract class WiredModemElement implements IWiredElement +{ + private final IWiredNode node = new WiredNode( this ); + private final Map remotePeripherals = new HashMap<>(); + + @Nonnull + @Override + public IWiredNode getNode() + { + return node; + } + + @Nonnull + @Override + public String getSenderID() + { + return "modem"; + } + + @Override + public void networkChanged( @Nonnull IWiredNetworkChange change ) + { + synchronized( remotePeripherals ) + { + remotePeripherals.keySet().removeAll( change.peripheralsRemoved().keySet() ); + for( String name : change.peripheralsRemoved().keySet() ) + { + detachPeripheral( name ); + } + + for( Map.Entry peripheral : change.peripheralsAdded().entrySet() ) + { + attachPeripheral( peripheral.getKey(), peripheral.getValue() ); + } + remotePeripherals.putAll( change.peripheralsAdded() ); + } + } + + public Map getRemotePeripherals() + { + return remotePeripherals; + } + + protected abstract void attachPeripheral( String name, IPeripheral peripheral ); + + protected abstract void detachPeripheral( String name ); +} diff --git a/remappedSrc/dan200/computercraft/shared/peripheral/modem/wired/WiredModemLocalPeripheral.java b/remappedSrc/dan200/computercraft/shared/peripheral/modem/wired/WiredModemLocalPeripheral.java new file mode 100644 index 000000000..82b99ddba --- /dev/null +++ b/remappedSrc/dan200/computercraft/shared/peripheral/modem/wired/WiredModemLocalPeripheral.java @@ -0,0 +1,144 @@ +/* + * 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.shared.peripheral.modem.wired; + +import dan200.computercraft.ComputerCraft; +import dan200.computercraft.api.peripheral.IPeripheral; +import dan200.computercraft.shared.Peripherals; +import dan200.computercraft.shared.util.IDAssigner; +import dan200.computercraft.shared.util.NBTUtil; +import net.minecraft.block.Block; +import net.minecraft.nbt.CompoundTag; +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; +import java.util.Collections; +import java.util.Map; + +/** + * Represents a local peripheral exposed on the wired network + * + * This is responsible for getting the peripheral in world, tracking id and type and determining whether + * it has changed. + */ +public final class WiredModemLocalPeripheral +{ + private static final String NBT_PERIPHERAL_TYPE = "PeripheralType"; + private static final String NBT_PERIPHERAL_ID = "PeripheralId"; + + private int id; + private String type; + + private IPeripheral peripheral; + + /** + * Attach a new peripheral from the world + * + * @param world The world to search in + * @param origin The position to search from + * @param direction The direction so search in + * @return Whether the peripheral changed. + */ + public boolean attach( @Nonnull World world, @Nonnull BlockPos origin, @Nonnull Direction direction ) + { + IPeripheral oldPeripheral = peripheral; + IPeripheral peripheral = this.peripheral = getPeripheralFrom( world, origin, direction ); + + if( peripheral == null ) + { + return oldPeripheral != null; + } + else + { + String type = peripheral.getType(); + int id = this.id; + + if( id > 0 && this.type == null ) + { + // If we had an ID but no type, then just set the type. + this.type = type; + } + else if( id < 0 || !type.equals( this.type ) ) + { + this.type = type; + this.id = IDAssigner.getNextId( world, "peripheral." + type ); + } + + return oldPeripheral == null || !oldPeripheral.equals( peripheral ); + } + } + + /** + * Detach the current peripheral + * + * @return Whether the peripheral changed + */ + public boolean detach() + { + if( peripheral == null ) return false; + peripheral = null; + return true; + } + + @Nullable + public String getConnectedName() + { + return peripheral != null ? type + "_" + id : null; + } + + @Nullable + public IPeripheral getPeripheral() + { + return peripheral; + } + + public boolean hasPeripheral() + { + return peripheral != null; + } + + public void extendMap( @Nonnull Map peripherals ) + { + if( peripheral != null ) peripherals.put( type + "_" + id, peripheral ); + } + + public Map toMap() + { + return peripheral == null + ? Collections.emptyMap() + : Collections.singletonMap( type + "_" + id, peripheral ); + } + + public void toTag( @Nonnull CompoundTag tag, @Nonnull String suffix ) + { + if( id >= 0 ) tag.putInt( NBT_PERIPHERAL_ID + suffix, id ); + if( type != null ) tag.putString( NBT_PERIPHERAL_TYPE + suffix, type ); + } + + public void fromTag( @Nonnull CompoundTag tag, @Nonnull String suffix ) + { + id = tag.contains( NBT_PERIPHERAL_ID + suffix, NBTUtil.TAG_ANY_NUMERIC ) + ? tag.getInt( NBT_PERIPHERAL_ID + suffix ) : -1; + + type = tag.contains( NBT_PERIPHERAL_TYPE + suffix, NBTUtil.TAG_STRING ) + ? tag.getString( NBT_PERIPHERAL_TYPE + suffix ) : null; + } + + private static IPeripheral getPeripheralFrom( World world, BlockPos pos, Direction direction ) + { + BlockPos offset = pos.offset( direction ); + + Block block = world.getBlockState( offset ).getBlock(); + if( block == ComputerCraft.Blocks.wiredModemFull || block == ComputerCraft.Blocks.cable ) return null; + + IPeripheral peripheral = Peripherals.getPeripheral( world, offset, direction.getOpposite() ); + return peripheral instanceof WiredModemPeripheral ? null : peripheral; + } +} diff --git a/remappedSrc/dan200/computercraft/shared/peripheral/modem/wired/WiredModemPeripheral.java b/remappedSrc/dan200/computercraft/shared/peripheral/modem/wired/WiredModemPeripheral.java new file mode 100644 index 000000000..2c76c05d5 --- /dev/null +++ b/remappedSrc/dan200/computercraft/shared/peripheral/modem/wired/WiredModemPeripheral.java @@ -0,0 +1,412 @@ +/* + * 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.shared.peripheral.modem.wired; + +import com.google.common.collect.ImmutableMap; +import dan200.computercraft.api.filesystem.IMount; +import dan200.computercraft.api.filesystem.IWritableMount; +import dan200.computercraft.api.lua.ILuaContext; +import dan200.computercraft.api.lua.LuaException; +import dan200.computercraft.api.network.IPacketNetwork; +import dan200.computercraft.api.network.wired.IWiredNode; +import dan200.computercraft.api.network.wired.IWiredSender; +import dan200.computercraft.api.peripheral.IComputerAccess; +import dan200.computercraft.api.peripheral.IPeripheral; +import dan200.computercraft.api.peripheral.IWorkMonitor; +import dan200.computercraft.shared.peripheral.modem.ModemPeripheral; +import dan200.computercraft.shared.peripheral.modem.ModemState; +import net.minecraft.world.World; + +import javax.annotation.Nonnull; +import javax.annotation.Nullable; +import java.util.HashMap; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; + +import static dan200.computercraft.core.apis.ArgumentHelper.getString; + +public abstract class WiredModemPeripheral extends ModemPeripheral implements IWiredSender +{ + private final WiredModemElement modem; + + private final Map> peripheralWrappers = new HashMap<>( 1 ); + + public WiredModemPeripheral( ModemState state, WiredModemElement modem ) + { + super( state ); + this.modem = modem; + } + + //region IPacketSender implementation + @Override + public boolean isInterdimensional() + { + return false; + } + + @Override + public double getRange() + { + return 256.0; + } + + @Override + protected IPacketNetwork getNetwork() + { + return modem.getNode(); + } + + @Nonnull + @Override + public World getWorld() + { + return modem.getWorld(); + } + + @Nonnull + protected abstract WiredModemLocalPeripheral getLocalPeripheral(); + //endregion + + //region IPeripheral + @Nonnull + @Override + public String[] getMethodNames() + { + String[] methods = super.getMethodNames(); + String[] newMethods = new String[methods.length + 6]; + System.arraycopy( methods, 0, newMethods, 0, methods.length ); + newMethods[methods.length] = "getNamesRemote"; + newMethods[methods.length + 1] = "isPresentRemote"; + newMethods[methods.length + 2] = "getTypeRemote"; + newMethods[methods.length + 3] = "getMethodsRemote"; + newMethods[methods.length + 4] = "callRemote"; + newMethods[methods.length + 5] = "getNameLocal"; + return newMethods; + } + + @Override + public Object[] callMethod( @Nonnull IComputerAccess computer, @Nonnull ILuaContext context, int method, @Nonnull Object[] arguments ) throws LuaException, InterruptedException + { + String[] methods = super.getMethodNames(); + switch( method - methods.length ) + { + case 0: + { + // getNamesRemote + Map wrappers = getWrappers( computer ); + Map table = new HashMap<>(); + if( wrappers != null ) + { + int idx = 1; + for( String name : wrappers.keySet() ) table.put( idx++, name ); + } + return new Object[] { table }; + } + case 1: + { + // isPresentRemote + String name = getString( arguments, 0 ); + return new Object[] { getWrapper( computer, name ) != null }; + } + case 2: + { + // getTypeRemote + String name = getString( arguments, 0 ); + RemotePeripheralWrapper wrapper = getWrapper( computer, name ); + return wrapper != null ? new Object[] { wrapper.getType() } : null; + } + case 3: + { + // getMethodsRemote + String name = getString( arguments, 0 ); + RemotePeripheralWrapper wrapper = getWrapper( computer, name ); + if( wrapper == null ) return null; + + String[] methodNames = wrapper.getMethodNames(); + Map table = new HashMap<>(); + for( int i = 0; i < methodNames.length; i++ ) + { + table.put( i + 1, methodNames[i] ); + } + return new Object[] { table }; + } + case 4: + { + // callRemote + String remoteName = getString( arguments, 0 ); + String methodName = getString( arguments, 1 ); + RemotePeripheralWrapper wrapper = getWrapper( computer, remoteName ); + if( wrapper == null ) throw new LuaException( "No peripheral: " + remoteName ); + + Object[] methodArgs = new Object[arguments.length - 2]; + System.arraycopy( arguments, 2, methodArgs, 0, arguments.length - 2 ); + return wrapper.callMethod( context, methodName, methodArgs ); + } + case 5: + { + // getNameLocal + String local = getLocalPeripheral().getConnectedName(); + return local == null ? null : new Object[] { local }; + } + default: + { + // The regular modem methods + return super.callMethod( computer, context, method, arguments ); + } + } + } + + @Override + public void attach( @Nonnull IComputerAccess computer ) + { + super.attach( computer ); + + ConcurrentMap wrappers; + synchronized( peripheralWrappers ) + { + wrappers = peripheralWrappers.get( computer ); + if( wrappers == null ) peripheralWrappers.put( computer, wrappers = new ConcurrentHashMap<>() ); + } + + synchronized( modem.getRemotePeripherals() ) + { + for( Map.Entry entry : modem.getRemotePeripherals().entrySet() ) + { + attachPeripheralImpl( computer, wrappers, entry.getKey(), entry.getValue() ); + } + } + } + + @Override + public void detach( @Nonnull IComputerAccess computer ) + { + Map wrappers; + synchronized( peripheralWrappers ) + { + wrappers = peripheralWrappers.remove( computer ); + } + if( wrappers != null ) + { + for( RemotePeripheralWrapper wrapper : wrappers.values() ) wrapper.detach(); + wrappers.clear(); + } + + super.detach( computer ); + } + + @Override + public boolean equals( IPeripheral other ) + { + if( other instanceof WiredModemPeripheral ) + { + WiredModemPeripheral otherModem = (WiredModemPeripheral) other; + return otherModem.modem == modem; + } + return false; + } + //endregion + + @Nonnull + @Override + public IWiredNode getNode() + { + return modem.getNode(); + } + + public void attachPeripheral( String name, IPeripheral peripheral ) + { + synchronized( peripheralWrappers ) + { + for( Map.Entry> entry : peripheralWrappers.entrySet() ) + { + attachPeripheralImpl( entry.getKey(), entry.getValue(), name, peripheral ); + } + } + } + + public void detachPeripheral( String name ) + { + synchronized( peripheralWrappers ) + { + for( ConcurrentMap wrappers : peripheralWrappers.values() ) + { + RemotePeripheralWrapper wrapper = wrappers.remove( name ); + if( wrapper != null ) wrapper.detach(); + } + + } + } + + private void attachPeripheralImpl( IComputerAccess computer, ConcurrentMap peripherals, String periphName, IPeripheral peripheral ) + { + if( !peripherals.containsKey( periphName ) && !periphName.equals( getLocalPeripheral().getConnectedName() ) ) + { + RemotePeripheralWrapper wrapper = new RemotePeripheralWrapper( modem, peripheral, computer, periphName ); + peripherals.put( periphName, wrapper ); + wrapper.attach(); + } + } + + private ConcurrentMap getWrappers( IComputerAccess computer ) + { + synchronized( peripheralWrappers ) + { + return peripheralWrappers.get( computer ); + } + } + + private RemotePeripheralWrapper getWrapper( IComputerAccess computer, String remoteName ) + { + ConcurrentMap wrappers = getWrappers( computer ); + return wrappers == null ? null : wrappers.get( remoteName ); + } + + private static class RemotePeripheralWrapper implements IComputerAccess + { + private final WiredModemElement m_element; + private final IPeripheral m_peripheral; + private final IComputerAccess m_computer; + private final String m_name; + + private final String m_type; + private final String[] m_methods; + private final Map m_methodMap; + + public RemotePeripheralWrapper( WiredModemElement element, IPeripheral peripheral, IComputerAccess computer, String name ) + { + m_element = element; + m_peripheral = peripheral; + m_computer = computer; + m_name = name; + + 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 void attach() + { + m_peripheral.attach( this ); + m_computer.queueEvent( "peripheral", new Object[] { getAttachmentName() } ); + } + + public void detach() + { + m_peripheral.detach( this ); + m_computer.queueEvent( "peripheral_detach", new Object[] { getAttachmentName() } ); + } + + public String getType() + { + return m_type; + } + + public String[] getMethodNames() + { + return m_methods; + } + + public Object[] callMethod( ILuaContext context, String methodName, Object[] arguments ) throws LuaException, InterruptedException + { + if( m_methodMap.containsKey( methodName ) ) + { + int method = m_methodMap.get( methodName ); + return m_peripheral.callMethod( this, context, method, arguments ); + } + throw new LuaException( "No such method " + methodName ); + } + + // IComputerAccess implementation + + @Override + public String mount( @Nonnull String desiredLocation, @Nonnull IMount mount ) + { + return m_computer.mount( desiredLocation, mount, m_name ); + } + + @Override + public String mount( @Nonnull String desiredLocation, @Nonnull IMount mount, @Nonnull String driveName ) + { + return m_computer.mount( desiredLocation, mount, driveName ); + } + + @Override + public String mountWritable( @Nonnull String desiredLocation, @Nonnull IWritableMount mount ) + { + return m_computer.mountWritable( desiredLocation, mount, m_name ); + } + + @Override + public String mountWritable( @Nonnull String desiredLocation, @Nonnull IWritableMount mount, @Nonnull String driveName ) + { + return m_computer.mountWritable( desiredLocation, mount, driveName ); + } + + @Override + public void unmount( String location ) + { + m_computer.unmount( location ); + } + + @Override + public int getID() + { + return m_computer.getID(); + } + + @Override + public void queueEvent( @Nonnull String event, Object[] arguments ) + { + m_computer.queueEvent( event, arguments ); + } + + @Nullable + @Override + public IWorkMonitor getMainThreadMonitor() + { + return m_computer.getMainThreadMonitor(); + } + + @Nonnull + @Override + public String getAttachmentName() + { + return m_name; + } + + @Nonnull + @Override + public Map getAvailablePeripherals() + { + synchronized( m_element.getRemotePeripherals() ) + { + return ImmutableMap.copyOf( m_element.getRemotePeripherals() ); + } + } + + @Nullable + @Override + public IPeripheral getAvailablePeripheral( @Nonnull String name ) + { + synchronized( m_element.getRemotePeripherals() ) + { + return m_element.getRemotePeripherals().get( name ); + } + } + } +} diff --git a/remappedSrc/dan200/computercraft/shared/peripheral/modem/wireless/BlockWirelessModem.java b/remappedSrc/dan200/computercraft/shared/peripheral/modem/wireless/BlockWirelessModem.java new file mode 100644 index 000000000..43e715c85 --- /dev/null +++ b/remappedSrc/dan200/computercraft/shared/peripheral/modem/wireless/BlockWirelessModem.java @@ -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.shared.peripheral.modem.wireless; + +import dan200.computercraft.shared.common.BlockGeneric; +import dan200.computercraft.shared.peripheral.modem.ModemShapes; +import dan200.computercraft.shared.util.NamedBlockEntityType; +import dan200.computercraft.shared.util.WaterloggableBlock; +import net.minecraft.block.Block; +import net.minecraft.block.BlockState; +import net.minecraft.block.ShapeContext; +import net.minecraft.fluid.FluidState; +import net.minecraft.item.ItemPlacementContext; +import net.minecraft.state.StateManager; +import net.minecraft.state.property.BooleanProperty; +import net.minecraft.state.property.DirectionProperty; +import net.minecraft.state.property.Properties; +import net.minecraft.util.math.BlockPos; +import net.minecraft.util.math.Direction; +import net.minecraft.util.shape.VoxelShape; +import net.minecraft.world.BlockView; +import net.minecraft.world.CollisionView; +import net.minecraft.world.WorldAccess; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class BlockWirelessModem extends BlockGeneric implements WaterloggableBlock +{ + public static final DirectionProperty FACING = Properties.FACING; + public static final BooleanProperty ON = BooleanProperty.of( "on" ); + + public BlockWirelessModem( Settings settings, NamedBlockEntityType type ) + { + super( settings, type ); + setDefaultState( getStateManager().getDefaultState() + .with( FACING, Direction.NORTH ) + .with( ON, false ) + .with( WATERLOGGED, false ) ); + } + + @Override + protected void appendProperties( StateManager.Builder builder ) + { + builder.add( FACING, ON, WATERLOGGED ); + } + + @Nonnull + @Override + @Deprecated + public VoxelShape getOutlineShape( BlockState blockState, BlockView world, BlockPos pos, ShapeContext position ) + { + return ModemShapes.getBounds( blockState.get( FACING ) ); + } + + @Nonnull + @Override + @Deprecated + public FluidState getFluidState( BlockState state ) + { + return getWaterloggedFluidState( state ); + } + + @Nonnull + @Override + @Deprecated + public BlockState getStateForNeighborUpdate( BlockState state, Direction side, BlockState otherState, WorldAccess world, BlockPos pos, BlockPos otherPos ) + { + updateWaterloggedPostPlacement( state, world, pos ); + return side == state.get( FACING ) && !state.canPlaceAt( world, pos ) + ? state.getFluidState().getBlockState() + : state; + } + + @Override + @Deprecated + public boolean canPlaceAt( BlockState state, CollisionView world, BlockPos pos ) + { + Direction facing = state.get( FACING ); + BlockPos offsetPos = pos.offset( facing ); + BlockState offsetState = world.getBlockState( offsetPos ); + return Block.isFaceFullSquare( offsetState.getCollisionShape( world, offsetPos ), facing.getOpposite() ); + } + + @Nullable + @Override + public BlockState getPlacementState( ItemPlacementContext placement ) + { + return getDefaultState() + .with( FACING, placement.getSide().getOpposite() ) + .with( WATERLOGGED, getWaterloggedStateForPlacement( placement ) ); + } +} diff --git a/remappedSrc/dan200/computercraft/shared/peripheral/modem/wireless/TileWirelessModem.java b/remappedSrc/dan200/computercraft/shared/peripheral/modem/wireless/TileWirelessModem.java new file mode 100644 index 000000000..01524bd56 --- /dev/null +++ b/remappedSrc/dan200/computercraft/shared/peripheral/modem/wireless/TileWirelessModem.java @@ -0,0 +1,158 @@ +/* + * 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.shared.peripheral.modem.wireless; + +import dan200.computercraft.ComputerCraft; +import dan200.computercraft.api.peripheral.IPeripheral; +import dan200.computercraft.api.peripheral.IPeripheralTile; +import dan200.computercraft.shared.common.TileGeneric; +import dan200.computercraft.shared.peripheral.modem.ModemPeripheral; +import dan200.computercraft.shared.peripheral.modem.ModemState; +import dan200.computercraft.shared.util.NamedBlockEntityType; +import dan200.computercraft.shared.util.TickScheduler; +import net.minecraft.block.BlockState; +import net.minecraft.block.entity.BlockEntityType; +import net.minecraft.util.Identifier; +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; + +public class TileWirelessModem extends TileGeneric implements IPeripheralTile +{ + public static final NamedBlockEntityType FACTORY_NORMAL = NamedBlockEntityType.create( + new Identifier( ComputerCraft.MOD_ID, "wireless_modem_normal" ), + f -> new TileWirelessModem( f, false ) + ); + + public static final NamedBlockEntityType FACTORY_ADVANCED = NamedBlockEntityType.create( + new Identifier( ComputerCraft.MOD_ID, "wireless_modem_advanced" ), + f -> new TileWirelessModem( f, true ) + ); + + private static class Peripheral extends WirelessModemPeripheral + { + private final TileWirelessModem entity; + + Peripheral( TileWirelessModem entity ) + { + super( new ModemState( () -> TickScheduler.schedule( entity ) ), entity.advanced ); + this.entity = entity; + } + + @Nonnull + @Override + public World getWorld() + { + return entity.getWorld(); + } + + @Nonnull + @Override + public Vec3d getPosition() + { + BlockPos pos = entity.getPos().offset( entity.modemDirection ); + return new Vec3d( pos.getX(), pos.getY(), pos.getZ() ); + } + + @Override + public boolean equals( IPeripheral other ) + { + return this == other || (other instanceof Peripheral && entity == ((Peripheral) other).entity); + } + } + + private final boolean advanced; + + private boolean hasModemDirection = false; + private Direction modemDirection = Direction.DOWN; + private final ModemPeripheral modem; + private boolean destroyed = false; + + public TileWirelessModem( BlockEntityType type, boolean advanced ) + { + super( type ); + this.advanced = advanced; + modem = new Peripheral( this ); + } + + @Override + public void cancelRemoval() + { + super.cancelRemoval(); + TickScheduler.schedule( this ); + } + + @Override + public void destroy() + { + if( !destroyed ) + { + modem.destroy(); + destroyed = true; + } + } + + @Override + public void markDirty() + { + super.markDirty(); + if( world != null ) + { + updateDirection(); + } + else + { + hasModemDirection = false; + } + } + + @Override + public void resetBlock() + { + super.resetBlock(); + hasModemDirection = false; + world.getBlockTickScheduler().schedule( getPos(), getCachedState().getBlock(), 0 ); + } + + @Override + public void blockTick() + { + updateDirection(); + + if( modem.getModemState().pollChanged() ) updateBlockState(); + } + + private void updateDirection() + { + if( hasModemDirection ) return; + + hasModemDirection = true; + modemDirection = getCachedState().get( BlockWirelessModem.FACING ); + } + + private void updateBlockState() + { + boolean on = modem.getModemState().isOpen(); + BlockState state = getCachedState(); + if( state.get( BlockWirelessModem.ON ) != on ) + { + getWorld().setBlockState( getPos(), state.with( BlockWirelessModem.ON, on ) ); + } + } + + @Nullable + @Override + public IPeripheral getPeripheral( @Nonnull Direction side ) + { + updateDirection(); + return side == modemDirection ? modem : null; + } +} diff --git a/remappedSrc/dan200/computercraft/shared/peripheral/modem/wireless/WirelessModemPeripheral.java b/remappedSrc/dan200/computercraft/shared/peripheral/modem/wireless/WirelessModemPeripheral.java new file mode 100644 index 000000000..2ff323829 --- /dev/null +++ b/remappedSrc/dan200/computercraft/shared/peripheral/modem/wireless/WirelessModemPeripheral.java @@ -0,0 +1,67 @@ +/* + * 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.shared.peripheral.modem.wireless; + +import dan200.computercraft.ComputerCraft; +import dan200.computercraft.api.network.IPacketNetwork; +import dan200.computercraft.shared.peripheral.modem.ModemPeripheral; +import dan200.computercraft.shared.peripheral.modem.ModemState; +import net.minecraft.util.math.Vec3d; +import net.minecraft.world.World; + +public abstract class WirelessModemPeripheral extends ModemPeripheral +{ + private boolean m_advanced; + + public WirelessModemPeripheral( ModemState state, boolean advanced ) + { + super( state ); + m_advanced = advanced; + } + + @Override + public boolean isInterdimensional() + { + return m_advanced; + } + + @Override + public double getRange() + { + if( m_advanced ) + { + return Integer.MAX_VALUE; + } + else + { + World world = getWorld(); + if( world != null ) + { + Vec3d position = getPosition(); + double minRange = ComputerCraft.modem_range; + double maxRange = ComputerCraft.modem_highAltitudeRange; + if( world.isRaining() && world.isThundering() ) + { + minRange = ComputerCraft.modem_rangeDuringStorm; + maxRange = ComputerCraft.modem_highAltitudeRangeDuringStorm; + } + if( position.y > 96.0 && maxRange > minRange ) + { + return minRange + (position.y - 96.0) * ((maxRange - minRange) / ((world.getHeight() - 1) - 96.0)); + } + return minRange; + } + return 0.0; + } + } + + @Override + protected IPacketNetwork getNetwork() + { + return WirelessNetwork.getUniversal(); + } +} diff --git a/remappedSrc/dan200/computercraft/shared/peripheral/modem/wireless/WirelessNetwork.java b/remappedSrc/dan200/computercraft/shared/peripheral/modem/wireless/WirelessNetwork.java new file mode 100644 index 000000000..db5ca7f61 --- /dev/null +++ b/remappedSrc/dan200/computercraft/shared/peripheral/modem/wireless/WirelessNetwork.java @@ -0,0 +1,94 @@ +/* + * 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.shared.peripheral.modem.wireless; + +import dan200.computercraft.api.network.IPacketNetwork; +import dan200.computercraft.api.network.IPacketReceiver; +import dan200.computercraft.api.network.IPacketSender; +import dan200.computercraft.api.network.Packet; + +import javax.annotation.Nonnull; +import java.util.Collections; +import java.util.Objects; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; + +public class WirelessNetwork implements IPacketNetwork +{ + private static WirelessNetwork s_universalNetwork = null; + + public static WirelessNetwork getUniversal() + { + if( s_universalNetwork == null ) + { + s_universalNetwork = new WirelessNetwork(); + } + return s_universalNetwork; + } + + public static void resetNetworks() + { + s_universalNetwork = null; + } + + private final Set m_receivers = Collections.newSetFromMap( new ConcurrentHashMap<>() ); + + @Override + public void addReceiver( @Nonnull IPacketReceiver receiver ) + { + Objects.requireNonNull( receiver, "device cannot be null" ); + m_receivers.add( receiver ); + } + + @Override + public void removeReceiver( @Nonnull IPacketReceiver receiver ) + { + Objects.requireNonNull( receiver, "device cannot be null" ); + m_receivers.remove( receiver ); + } + + @Override + public void transmitSameDimension( @Nonnull Packet packet, double range ) + { + Objects.requireNonNull( packet, "packet cannot be null" ); + for( IPacketReceiver device : m_receivers ) tryTransmit( device, packet, range, false ); + } + + @Override + public void transmitInterdimensional( @Nonnull Packet packet ) + { + Objects.requireNonNull( packet, "packet cannot be null" ); + for( IPacketReceiver device : m_receivers ) tryTransmit( device, packet, 0, true ); + } + + private static void tryTransmit( IPacketReceiver receiver, Packet packet, double range, boolean interdimensional ) + { + IPacketSender sender = packet.getSender(); + if( receiver.getWorld() == sender.getWorld() ) + { + double receiveRange = Math.max( range, receiver.getRange() ); // Ensure range is symmetrical + double distanceSq = receiver.getPosition().squaredDistanceTo( sender.getPosition() ); + if( interdimensional || receiver.isInterdimensional() || distanceSq <= receiveRange * receiveRange ) + { + receiver.receiveSameDimension( packet, Math.sqrt( distanceSq ) ); + } + } + else + { + if( interdimensional || receiver.isInterdimensional() ) + { + receiver.receiveDifferentDimension( packet ); + } + } + } + + @Override + public boolean isWireless() + { + return true; + } +} diff --git a/remappedSrc/dan200/computercraft/shared/peripheral/monitor/BlockMonitor.java b/remappedSrc/dan200/computercraft/shared/peripheral/monitor/BlockMonitor.java new file mode 100644 index 000000000..e03edc3f6 --- /dev/null +++ b/remappedSrc/dan200/computercraft/shared/peripheral/monitor/BlockMonitor.java @@ -0,0 +1,99 @@ +/* + * 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.shared.peripheral.monitor; + +import dan200.computercraft.shared.common.BlockGeneric; +import dan200.computercraft.shared.common.TileGeneric; +import dan200.computercraft.shared.util.NamedBlockEntityType; +import net.minecraft.block.Block; +import net.minecraft.block.BlockState; +import net.minecraft.block.entity.BlockEntity; +import net.minecraft.client.render.RenderLayer; +import net.minecraft.entity.LivingEntity; +import net.minecraft.item.ItemPlacementContext; +import net.minecraft.item.ItemStack; +import net.minecraft.state.StateManager; +import net.minecraft.state.property.DirectionProperty; +import net.minecraft.state.property.EnumProperty; +import net.minecraft.state.property.Properties; +import net.minecraft.util.math.BlockPos; +import net.minecraft.util.math.Direction; +import net.minecraft.world.World; + +import javax.annotation.Nullable; + +public class BlockMonitor extends BlockGeneric +{ + public static final DirectionProperty ORIENTATION = DirectionProperty.of( "orientation", + Direction.UP, Direction.DOWN, Direction.NORTH ); + + public static final DirectionProperty FACING = Properties.HORIZONTAL_FACING; + + static final EnumProperty STATE = EnumProperty.of( "state", MonitorEdgeState.class ); + + public BlockMonitor( Settings settings, NamedBlockEntityType type ) + { + super( settings, type ); + setDefaultState( getStateManager().getDefaultState() + .with( ORIENTATION, Direction.NORTH ) + .with( FACING, Direction.NORTH ) + .with( STATE, MonitorEdgeState.NONE ) ); + } + + @Override + public RenderLayer getRenderLayer() + { + return RenderLayer.CUTOUT; + } + + @Override + protected void appendProperties( StateManager.Builder builder ) + { + builder.add( ORIENTATION, FACING, STATE ); + } + + @Override + @Nullable + public BlockState getPlacementState( ItemPlacementContext context ) + { + float pitch = context.getPlayer() == null ? 0 : context.getPlayer().pitch; + Direction orientation; + if( pitch > 66.5f ) + { + // If the player is looking down, place it facing upwards + orientation = Direction.UP; + } + else if( pitch < -66.5f ) + { + // If they're looking up, place it down. + orientation = Direction.DOWN; + } + else + { + orientation = Direction.NORTH; + } + + return getDefaultState() + .with( FACING, context.getPlayerFacing().getOpposite() ) + .with( ORIENTATION, orientation ); + } + + @Override + public void onPlaced( World world, BlockPos pos, BlockState blockState, @Nullable LivingEntity livingEntity, ItemStack itemStack ) + { + super.onPlaced( world, pos, blockState, livingEntity, itemStack ); + + BlockEntity entity = world.getBlockEntity( pos ); + if( entity instanceof TileMonitor && !world.isClient ) + { + TileMonitor monitor = (TileMonitor) entity; + monitor.contractNeighbours(); + monitor.contract(); + monitor.expand(); + } + } +} diff --git a/remappedSrc/dan200/computercraft/shared/peripheral/monitor/ClientMonitor.java b/remappedSrc/dan200/computercraft/shared/peripheral/monitor/ClientMonitor.java new file mode 100644 index 000000000..6821a3e59 --- /dev/null +++ b/remappedSrc/dan200/computercraft/shared/peripheral/monitor/ClientMonitor.java @@ -0,0 +1,99 @@ +/* + * 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.shared.peripheral.monitor; + +import com.mojang.blaze3d.platform.GlStateManager; +import dan200.computercraft.shared.common.ClientTerminal; +import net.fabricmc.api.EnvType; +import net.fabricmc.api.Environment; +import net.minecraft.util.math.BlockPos; + +import java.util.HashSet; +import java.util.Iterator; +import java.util.Set; + +public class ClientMonitor extends ClientTerminal +{ + private static final Set allMonitors = new HashSet<>(); + + private final TileMonitor origin; + + public long lastRenderFrame = -1; + public BlockPos lastRenderPos = null; + public int[] renderDisplayLists = null; + + public ClientMonitor( boolean colour, TileMonitor origin ) + { + super( colour ); + this.origin = origin; + } + + public TileMonitor getOrigin() + { + return origin; + } + + @Environment( EnvType.CLIENT ) + public void createLists() + { + if( renderDisplayLists == null ) + { + renderDisplayLists = new int[3]; + + for( int i = 0; i < renderDisplayLists.length; i++ ) + { + renderDisplayLists[i] = GlStateManager.genLists( 1 ); + } + + synchronized( allMonitors ) + { + allMonitors.add( this ); + } + } + } + + @Environment( EnvType.CLIENT ) + public void destroy() + { + if( renderDisplayLists != null ) + { + synchronized( allMonitors ) + { + allMonitors.remove( this ); + } + + for( int list : renderDisplayLists ) + { + GlStateManager.deleteLists( list, 1 ); + } + + renderDisplayLists = null; + } + } + + @Environment( EnvType.CLIENT ) + public static void destroyAll() + { + synchronized( allMonitors ) + { + for( Iterator iterator = allMonitors.iterator(); iterator.hasNext(); ) + { + ClientMonitor monitor = iterator.next(); + if( monitor.renderDisplayLists != null ) + { + for( int list : monitor.renderDisplayLists ) + { + GlStateManager.deleteLists( list, 1 ); + } + monitor.renderDisplayLists = null; + } + + iterator.remove(); + } + } + } +} diff --git a/remappedSrc/dan200/computercraft/shared/peripheral/monitor/MonitorEdgeState.java b/remappedSrc/dan200/computercraft/shared/peripheral/monitor/MonitorEdgeState.java new file mode 100644 index 000000000..c03c2106a --- /dev/null +++ b/remappedSrc/dan200/computercraft/shared/peripheral/monitor/MonitorEdgeState.java @@ -0,0 +1,75 @@ +/* + * 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.shared.peripheral.monitor; + +import net.minecraft.util.StringIdentifiable; + +import javax.annotation.Nonnull; + +import static dan200.computercraft.shared.peripheral.monitor.MonitorEdgeState.Flags.*; + +public enum MonitorEdgeState implements StringIdentifiable +{ + NONE( "none", 0 ), + + L( "l", LEFT ), + R( "r", RIGHT ), + LR( "lr", LEFT | RIGHT ), + U( "u", UP ), + D( "d", DOWN ), + + UD( "ud", UP | DOWN ), + RD( "rd", RIGHT | DOWN ), + LD( "ld", LEFT | DOWN ), + RU( "ru", RIGHT | UP ), + LU( "lu", LEFT | UP ), + + LRD( "lrd", LEFT | RIGHT | DOWN ), + RUD( "rud", RIGHT | UP | DOWN ), + LUD( "lud", LEFT | UP | DOWN ), + LRU( "lru", LEFT | RIGHT | UP ), + LRUD( "lrud", LEFT | RIGHT | UP | DOWN ); + + private final String name; + private final int flags; + + MonitorEdgeState( String name, int flags ) + { + this.name = name; + this.flags = flags; + } + + private static final MonitorEdgeState[] BY_FLAG = new MonitorEdgeState[16]; + + static + { + for( MonitorEdgeState state : values() ) + { + BY_FLAG[state.flags] = state; + } + } + + public static MonitorEdgeState fromConnections( boolean up, boolean down, boolean left, boolean right ) + { + return BY_FLAG[(up ? UP : 0) | (down ? DOWN : 0) | (left ? LEFT : 0) | (right ? RIGHT : 0)]; + } + + @Nonnull + @Override + public String asString() + { + return name; + } + + static final class Flags + { + static final int UP = 1 << 0; + static final int DOWN = 1 << 1; + static final int LEFT = 1 << 2; + static final int RIGHT = 1 << 3; + } +} diff --git a/remappedSrc/dan200/computercraft/shared/peripheral/monitor/MonitorPeripheral.java b/remappedSrc/dan200/computercraft/shared/peripheral/monitor/MonitorPeripheral.java new file mode 100644 index 000000000..5dac79574 --- /dev/null +++ b/remappedSrc/dan200/computercraft/shared/peripheral/monitor/MonitorPeripheral.java @@ -0,0 +1,235 @@ +/* + * 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.shared.peripheral.monitor; + +import dan200.computercraft.api.lua.ILuaContext; +import dan200.computercraft.api.lua.LuaException; +import dan200.computercraft.api.peripheral.IComputerAccess; +import dan200.computercraft.api.peripheral.IPeripheral; +import dan200.computercraft.core.apis.TermAPI; +import dan200.computercraft.core.terminal.Terminal; +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 MonitorPeripheral implements IPeripheral +{ + private final TileMonitor m_monitor; + + public MonitorPeripheral( TileMonitor monitor ) + { + m_monitor = monitor; + } + + @Nonnull + @Override + public String getType() + { + return "monitor"; + } + + @Nonnull + @Override + public String[] getMethodNames() + { + return new String[] { + "write", + "scroll", + "setCursorPos", + "setCursorBlink", + "getCursorPos", + "getSize", + "clear", + "clearLine", + "setTextScale", + "setTextColour", + "setTextColor", + "setBackgroundColour", + "setBackgroundColor", + "isColour", + "isColor", + "getTextColour", + "getTextColor", + "getBackgroundColour", + "getBackgroundColor", + "blit", + "setPaletteColour", + "setPaletteColor", + "getPaletteColour", + "getPaletteColor", + "getTextScale", + "getCursorBlink", + }; + } + + @Override + public Object[] callMethod( @Nonnull IComputerAccess computer, @Nonnull ILuaContext context, int method, @Nonnull Object[] args ) throws LuaException + { + ServerMonitor monitor = m_monitor.getCachedServerMonitor(); + if( monitor == null ) throw new LuaException( "Monitor has been detached" ); + + Terminal terminal = monitor.getTerminal(); + if( terminal == null ) throw new LuaException( "Monitor has been detached" ); + + switch( method ) + { + case 0: + { + // write + String text = args.length > 0 && args[0] != null ? args[0].toString() : ""; + terminal.write( text ); + terminal.setCursorPos( terminal.getCursorX() + text.length(), terminal.getCursorY() ); + return null; + } + case 1: + { + // scroll + int value = getInt( args, 0 ); + terminal.scroll( value ); + return null; + } + case 2: + { + // setCursorPos + int x = getInt( args, 0 ) - 1; + int y = getInt( args, 1 ) - 1; + terminal.setCursorPos( x, y ); + return null; + } + case 3: + { + // setCursorBlink + boolean blink = getBoolean( args, 0 ); + terminal.setCursorBlink( blink ); + return null; + } + case 4: // getCursorPos + return new Object[] { terminal.getCursorX() + 1, terminal.getCursorY() + 1 }; + case 5: // getSize + return new Object[] { terminal.getWidth(), terminal.getHeight() }; + case 6: // clear + terminal.clear(); + return null; + case 7: // clearLine + terminal.clearLine(); + return null; + case 8: + { + // setTextScale + int scale = (int) (getReal( args, 0 ) * 2.0); + if( scale < 1 || scale > 10 ) + { + throw new LuaException( "Expected number in range 0.5-5" ); + } + monitor.setTextScale( scale ); + return null; + } + case 9: + case 10: + { + // setTextColour/setTextColor + int colour = TermAPI.parseColour( args ); + terminal.setTextColour( colour ); + return null; + } + case 11: + case 12: + { + // setBackgroundColour/setBackgroundColor + int colour = TermAPI.parseColour( args ); + terminal.setBackgroundColour( colour ); + return null; + } + case 13: + case 14: // isColour/isColor + return new Object[] { monitor.isColour() }; + case 15: + case 16: // getTextColour/getTextColor + return TermAPI.encodeColour( terminal.getTextColour() ); + case 17: + case 18: // getBackgroundColour/getBackgroundColor + return TermAPI.encodeColour( terminal.getBackgroundColour() ); + case 19: + { + // 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" ); + } + + terminal.blit( text, textColour, backgroundColour ); + terminal.setCursorPos( terminal.getCursorX() + text.length(), terminal.getCursorY() ); + return null; + } + case 20: + case 21: + { + // setPaletteColour/setPaletteColor + int colour = 15 - TermAPI.parseColour( args ); + if( args.length == 2 ) + { + int hex = getInt( args, 1 ); + double[] rgb = Palette.decodeRGB8( hex ); + TermAPI.setColour( terminal, colour, rgb[0], rgb[1], rgb[2] ); + } + else + { + double r = getReal( args, 1 ); + double g = getReal( args, 2 ); + double b = getReal( args, 3 ); + TermAPI.setColour( terminal, colour, r, g, b ); + } + return null; + } + case 22: + case 23: + { + // getPaletteColour/getPaletteColor + Palette palette = terminal.getPalette(); + + int colour = 15 - TermAPI.parseColour( args ); + + if( palette != null ) + { + return ArrayUtils.toObject( palette.getColour( colour ) ); + } + return null; + } + case 24: // getTextScale + return new Object[] { monitor.getTextScale() / 2.0 }; + case 25: + // getCursorBlink + return new Object[] { terminal.getCursorBlink() }; + default: + return null; + } + } + + @Override + public void attach( @Nonnull IComputerAccess computer ) + { + m_monitor.addComputer( computer ); + } + + @Override + public void detach( @Nonnull IComputerAccess computer ) + { + m_monitor.removeComputer( computer ); + } + + @Override + public boolean equals( IPeripheral other ) + { + return other instanceof MonitorPeripheral && m_monitor == ((MonitorPeripheral) other).m_monitor; + } +} diff --git a/remappedSrc/dan200/computercraft/shared/peripheral/monitor/ServerMonitor.java b/remappedSrc/dan200/computercraft/shared/peripheral/monitor/ServerMonitor.java new file mode 100644 index 000000000..a6ace0f71 --- /dev/null +++ b/remappedSrc/dan200/computercraft/shared/peripheral/monitor/ServerMonitor.java @@ -0,0 +1,92 @@ +/* + * 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.shared.peripheral.monitor; + +import dan200.computercraft.core.terminal.Terminal; +import dan200.computercraft.shared.common.ServerTerminal; +import dan200.computercraft.shared.util.TickScheduler; + +import java.util.concurrent.atomic.AtomicBoolean; + +public class ServerMonitor extends ServerTerminal +{ + private final TileMonitor origin; + private int textScale = 2; + private final AtomicBoolean resized = new AtomicBoolean( false ); + private final AtomicBoolean changed = new AtomicBoolean( false ); + + public ServerMonitor( boolean colour, TileMonitor origin ) + { + super( colour ); + this.origin = origin; + } + + public synchronized void rebuild() + { + Terminal oldTerm = getTerminal(); + int oldWidth = oldTerm == null ? -1 : oldTerm.getWidth(); + int oldHeight = oldTerm == null ? -1 : oldTerm.getHeight(); + + double textScale = this.textScale * 0.5; + int termWidth = (int) Math.max( + Math.round( (origin.getWidth() - 2.0 * (TileMonitor.RENDER_BORDER + TileMonitor.RENDER_MARGIN)) / (textScale * 6.0 * TileMonitor.RENDER_PIXEL_SCALE) ), + 1.0 + ); + int termHeight = (int) Math.max( + Math.round( (origin.getHeight() - 2.0 * (TileMonitor.RENDER_BORDER + TileMonitor.RENDER_MARGIN)) / (textScale * 9.0 * TileMonitor.RENDER_PIXEL_SCALE) ), + 1.0 + ); + + resize( termWidth, termHeight ); + if( oldWidth != termWidth || oldHeight != termHeight ) + { + getTerminal().clear(); + resized.set( true ); + markChanged(); + } + } + + @Override + protected void markTerminalChanged() + { + super.markTerminalChanged(); + markChanged(); + } + + private void markChanged() + { + if( !changed.getAndSet( true ) ) TickScheduler.schedule( origin ); + } + + protected void clearChanged() + { + changed.set( false ); + } + + public int getTextScale() + { + return textScale; + } + + public synchronized void setTextScale( int textScale ) + { + if( this.textScale == textScale ) return; + this.textScale = textScale; + rebuild(); + } + + public boolean pollResized() + { + return resized.getAndSet( false ); + } + + public boolean pollTerminalChanged() + { + update(); + return hasTerminalChanged(); + } +} diff --git a/remappedSrc/dan200/computercraft/shared/peripheral/monitor/TileMonitor.java b/remappedSrc/dan200/computercraft/shared/peripheral/monitor/TileMonitor.java new file mode 100644 index 000000000..de642570e --- /dev/null +++ b/remappedSrc/dan200/computercraft/shared/peripheral/monitor/TileMonitor.java @@ -0,0 +1,683 @@ +/* + * 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.shared.peripheral.monitor; + +import dan200.computercraft.ComputerCraft; +import dan200.computercraft.api.peripheral.IComputerAccess; +import dan200.computercraft.api.peripheral.IPeripheral; +import dan200.computercraft.api.peripheral.IPeripheralTile; +import dan200.computercraft.core.terminal.Terminal; +import dan200.computercraft.shared.common.ServerTerminal; +import dan200.computercraft.shared.common.TileGeneric; +import dan200.computercraft.shared.util.NamedBlockEntityType; +import dan200.computercraft.shared.util.TickScheduler; +import net.minecraft.block.entity.BlockEntity; +import net.minecraft.block.entity.BlockEntityType; +import net.minecraft.entity.player.PlayerEntity; +import net.minecraft.nbt.CompoundTag; +import net.minecraft.util.Hand; +import net.minecraft.util.Identifier; +import net.minecraft.util.hit.BlockHitResult; +import net.minecraft.util.math.BlockPos; +import net.minecraft.util.math.Direction; +import net.minecraft.world.World; + +import javax.annotation.Nonnull; +import java.util.HashSet; +import java.util.Set; + +public class TileMonitor extends TileGeneric implements IPeripheralTile +{ + public static final NamedBlockEntityType FACTORY_NORMAL = NamedBlockEntityType.create( + new Identifier( ComputerCraft.MOD_ID, "monitor_normal" ), + f -> new TileMonitor( f, false ) + ); + + public static final NamedBlockEntityType FACTORY_ADVANCED = NamedBlockEntityType.create( + new Identifier( ComputerCraft.MOD_ID, "monitor_advanced" ), + f -> new TileMonitor( f, true ) + ); + + public static final double RENDER_BORDER = 2.0 / 16.0; + public static final double RENDER_MARGIN = 0.5 / 16.0; + public static final double RENDER_PIXEL_SCALE = 1.0 / 64.0; + + private static final int MAX_WIDTH = 8; + private static final int MAX_HEIGHT = 6; + + private static final String NBT_X = "XIndex"; + private static final String NBT_Y = "YIndex"; + private static final String NBT_WIDTH = "Width"; + private static final String NBT_HEIGHT = "Height"; + + private final boolean advanced; + + private ServerMonitor m_serverMonitor; + private ClientMonitor m_clientMonitor; + private MonitorPeripheral m_peripheral; + private final Set m_computers = new HashSet<>(); + + private boolean m_destroyed = false; + private boolean visiting = false; + + private int m_width = 1; + private int m_height = 1; + private int m_xIndex = 0; + private int m_yIndex = 0; + + public TileMonitor( BlockEntityType type, boolean advanced ) + { + super( type ); + this.advanced = advanced; + } + + @Override + public void cancelRemoval() + { + super.cancelRemoval(); + TickScheduler.schedule( this ); + } + + @Override + public void destroy() + { + // TODO: Call this before using the block + if( m_destroyed ) return; + m_destroyed = true; + if( !getWorld().isClient ) contractNeighbours(); + } + + @Override + public void markRemoved() + { + super.markRemoved(); + if( m_clientMonitor != null && m_xIndex == 0 && m_yIndex == 0 ) m_clientMonitor.destroy(); + } + + /* + @Override + public void onChunkUnloaded() + { + super.onChunkUnloaded(); + if( m_clientMonitor != null && m_xIndex == 0 && m_yIndex == 0 ) m_clientMonitor.destroy(); + } + */ + + @Override + public boolean onActivate( PlayerEntity player, Hand hand, BlockHitResult hit ) + { + if( !player.isSneaking() && getFront() == hit.getSide() ) + { + if( !getWorld().isClient ) + { + monitorTouched( + (float) (hit.getPos().x - hit.getBlockPos().getX()), + (float) (hit.getPos().y - hit.getBlockPos().getY()), + (float) (hit.getPos().z - hit.getBlockPos().getZ()) + ); + } + return true; + } + + return false; + } + + @Nonnull + @Override + public CompoundTag toTag( CompoundTag tag ) + { + tag.putInt( NBT_X, m_xIndex ); + tag.putInt( NBT_Y, m_yIndex ); + tag.putInt( NBT_WIDTH, m_width ); + tag.putInt( NBT_HEIGHT, m_height ); + return super.toTag( tag ); + } + + @Override + public void fromTag( CompoundTag tag ) + { + super.fromTag( tag ); + m_xIndex = tag.getInt( NBT_X ); + m_yIndex = tag.getInt( NBT_Y ); + m_width = tag.getInt( NBT_WIDTH ); + m_height = tag.getInt( NBT_HEIGHT ); + } + + @Override + public void blockTick() + { + if( m_xIndex != 0 || m_yIndex != 0 || m_serverMonitor == null ) return; + + m_serverMonitor.clearChanged(); + + if( m_serverMonitor.pollResized() ) + { + for( int x = 0; x < m_width; x++ ) + { + for( int y = 0; y < m_height; y++ ) + { + TileMonitor monitor = getNeighbour( x, y ); + if( monitor == null ) continue; + + for( IComputerAccess computer : monitor.m_computers ) + { + computer.queueEvent( "monitor_resize", new Object[] { + computer.getAttachmentName() + } ); + } + } + } + } + + if( m_serverMonitor.pollTerminalChanged() ) updateBlock(); + } + + // IPeripheralTile implementation + + @Override + public IPeripheral getPeripheral( @Nonnull Direction side ) + { + createServerMonitor(); // Ensure the monitor is created before doing anything else. + if( m_peripheral == null ) m_peripheral = new MonitorPeripheral( this ); + return m_peripheral; + } + + public ServerMonitor getCachedServerMonitor() + { + return m_serverMonitor; + } + + private ServerMonitor getServerMonitor() + { + if( m_serverMonitor != null ) return m_serverMonitor; + + TileMonitor origin = getOrigin(); + if( origin == null ) return null; + + return m_serverMonitor = origin.m_serverMonitor; + } + + private ServerMonitor createServerMonitor() + { + if( m_serverMonitor != null ) return m_serverMonitor; + + if( m_xIndex == 0 && m_yIndex == 0 ) + { + // If we're the origin, set up the new monitor + m_serverMonitor = new ServerMonitor( advanced, this ); + m_serverMonitor.rebuild(); + + // And propagate it to child monitors + for( int x = 0; x < m_width; x++ ) + { + for( int y = 0; y < m_height; y++ ) + { + TileMonitor monitor = getNeighbour( x, y ); + if( monitor != null ) monitor.m_serverMonitor = m_serverMonitor; + } + } + + return m_serverMonitor; + } + else + { + // Otherwise fetch the origin and attempt to get its monitor + // Note this may load chunks, but we don't really have a choice here. + BlockPos pos = getPos(); + BlockEntity te = world.getBlockEntity( pos.offset( getRight(), -m_xIndex ).offset( getDown(), -m_yIndex ) ); + if( !(te instanceof TileMonitor) ) return null; + + return m_serverMonitor = ((TileMonitor) te).createServerMonitor(); + } + } + + public ClientMonitor getClientMonitor() + { + if( m_clientMonitor != null ) return m_clientMonitor; + + BlockPos pos = getPos(); + BlockEntity te = world.getBlockEntity( pos.offset( getRight(), -m_xIndex ).offset( getDown(), -m_yIndex ) ); + if( !(te instanceof TileMonitor) ) return null; + + return m_clientMonitor = ((TileMonitor) te).m_clientMonitor; + } + + // Networking stuff + + @Override + protected void writeDescription( @Nonnull CompoundTag nbt ) + { + super.writeDescription( nbt ); + + nbt.putInt( NBT_X, m_xIndex ); + nbt.putInt( NBT_Y, m_yIndex ); + nbt.putInt( NBT_WIDTH, m_width ); + nbt.putInt( NBT_HEIGHT, m_height ); + + if( m_xIndex == 0 && m_yIndex == 0 && m_serverMonitor != null ) + { + m_serverMonitor.writeDescription( nbt ); + } + } + + @Override + protected final void readDescription( @Nonnull CompoundTag nbt ) + { + super.readDescription( nbt ); + + int oldXIndex = m_xIndex; + int oldYIndex = m_yIndex; + int oldWidth = m_width; + int oldHeight = m_height; + + m_xIndex = nbt.getInt( NBT_X ); + m_yIndex = nbt.getInt( NBT_Y ); + m_width = nbt.getInt( NBT_WIDTH ); + m_height = nbt.getInt( NBT_HEIGHT ); + + if( oldXIndex != m_xIndex || oldYIndex != m_yIndex ) + { + // If our index has changed then it's possible the origin monitor has changed. Thus + // we'll clear our cache. If we're the origin then we'll need to remove the glList as well. + if( oldXIndex == 0 && oldYIndex == 0 && m_clientMonitor != null ) m_clientMonitor.destroy(); + m_clientMonitor = null; + } + + if( m_xIndex == 0 && m_yIndex == 0 ) + { + // If we're the origin terminal then read the description + if( m_clientMonitor == null ) m_clientMonitor = new ClientMonitor( advanced, this ); + m_clientMonitor.readDescription( nbt ); + } + + if( oldXIndex != m_xIndex || oldYIndex != m_yIndex || + oldWidth != m_width || oldHeight != m_height ) + { + // One of our properties has changed, so ensure we redraw the block + updateBlock(); + } + } + + private void updateBlockState() + { + getWorld().setBlockState( getPos(), getCachedState() + .with( BlockMonitor.STATE, MonitorEdgeState.fromConnections( + m_yIndex < m_height - 1, m_yIndex > 0, + m_xIndex > 0, m_xIndex < m_width - 1 ) ), 2 ); + } + + // region Sizing and placement stuff + public Direction getDirection() + { + return getCachedState().get( BlockMonitor.FACING ); + } + + public Direction getOrientation() + { + return getCachedState().get( BlockMonitor.ORIENTATION ); + } + + public Direction getFront() + { + Direction orientation = getOrientation(); + return orientation == Direction.NORTH ? getDirection() : orientation; + } + + public Direction getRight() + { + return getDirection().rotateYCounterclockwise(); + } + + public Direction getDown() + { + Direction orientation = getOrientation(); + if( orientation == Direction.NORTH ) return Direction.UP; + return orientation == Direction.DOWN ? getDirection() : getDirection().getOpposite(); + } + + public int getWidth() + { + return m_width; + } + + public int getHeight() + { + return m_height; + } + + public int getXIndex() + { + return m_xIndex; + } + + public int getYIndex() + { + return m_yIndex; + } + + private TileMonitor getSimilarMonitorAt( BlockPos pos ) + { + if( pos.equals( getPos() ) ) return this; + + int y = pos.getY(); + World world = getWorld(); + if( world == null || !world.isBlockLoaded( pos ) ) return null; + + BlockEntity tile = world.getBlockEntity( pos ); + if( !(tile instanceof TileMonitor) ) return null; + + TileMonitor monitor = (TileMonitor) tile; + return !monitor.visiting && !monitor.m_destroyed && advanced == monitor.advanced + && getDirection() == monitor.getDirection() && getOrientation() == monitor.getOrientation() + ? monitor : null; + } + + private TileMonitor getNeighbour( int x, int y ) + { + BlockPos pos = getPos(); + Direction right = getRight(); + Direction down = getDown(); + int xOffset = -m_xIndex + x; + int yOffset = -m_yIndex + y; + return getSimilarMonitorAt( pos.offset( right, xOffset ).offset( down, yOffset ) ); + } + + private TileMonitor getOrigin() + { + return getNeighbour( 0, 0 ); + } + + private void resize( int width, int height ) + { + // If we're not already the origin then we'll need to generate a new terminal. + if( m_xIndex != 0 || m_yIndex != 0 ) m_serverMonitor = null; + + m_xIndex = 0; + m_yIndex = 0; + m_width = width; + m_height = height; + + // Determine if we actually need a monitor. In order to do this, simply check if + // any component monitor been wrapped as a peripheral. Whilst this flag may be + // out of date, + boolean needsTerminal = false; + terminalCheck: + for( int x = 0; x < width; x++ ) + { + for( int y = 0; y < height; y++ ) + { + TileMonitor monitor = getNeighbour( x, y ); + if( monitor != null && monitor.m_peripheral != null ) + { + needsTerminal = true; + break terminalCheck; + } + } + } + + // Either delete the current monitor or sync a new one. + if( needsTerminal ) + { + if( m_serverMonitor == null ) m_serverMonitor = new ServerMonitor( advanced, this ); + } + else + { + m_serverMonitor = null; + } + + // Update the terminal's width and height and rebuild it. This ensures the monitor + // is consistent when syncing it to other monitors. + if( m_serverMonitor != null ) m_serverMonitor.rebuild(); + + // Update the other monitors, setting coordinates, dimensions and the server terminal + for( int x = 0; x < width; x++ ) + { + for( int y = 0; y < height; y++ ) + { + TileMonitor monitor = getNeighbour( x, y ); + if( monitor == null ) continue; + + monitor.m_xIndex = x; + monitor.m_yIndex = y; + monitor.m_width = width; + monitor.m_height = height; + monitor.m_serverMonitor = m_serverMonitor; + monitor.updateBlockState(); + monitor.updateBlock(); + } + } + } + + private boolean mergeLeft() + { + TileMonitor left = getNeighbour( -1, 0 ); + if( left == null || left.m_yIndex != 0 || left.m_height != m_height ) return false; + + int width = left.m_width + m_width; + if( width > MAX_WIDTH ) return false; + + TileMonitor origin = left.getOrigin(); + if( origin != null ) origin.resize( width, m_height ); + left.expand(); + return true; + } + + private boolean mergeRight() + { + TileMonitor right = getNeighbour( m_width, 0 ); + if( right == null || right.m_yIndex != 0 || right.m_height != m_height ) return false; + + int width = m_width + right.m_width; + if( width > MAX_WIDTH ) return false; + + TileMonitor origin = getOrigin(); + if( origin != null ) origin.resize( width, m_height ); + expand(); + return true; + } + + private boolean mergeUp() + { + TileMonitor above = getNeighbour( 0, m_height ); + if( above == null || above.m_xIndex != 0 || above.m_width != m_width ) return false; + + int height = above.m_height + m_height; + if( height > MAX_HEIGHT ) return false; + + TileMonitor origin = getOrigin(); + if( origin != null ) origin.resize( m_width, height ); + expand(); + return true; + } + + private boolean mergeDown() + { + TileMonitor below = getNeighbour( 0, -1 ); + if( below == null || below.m_xIndex != 0 || below.m_width != m_width ) return false; + + int height = m_height + below.m_height; + if( height > MAX_HEIGHT ) return false; + + TileMonitor origin = below.getOrigin(); + if( origin != null ) origin.resize( m_width, height ); + below.expand(); + return true; + } + + @SuppressWarnings( "StatementWithEmptyBody" ) + void expand() + { + while( mergeLeft() || mergeRight() || mergeUp() || mergeDown() ) ; + } + + void contractNeighbours() + { + visiting = true; + if( m_xIndex > 0 ) + { + TileMonitor left = getNeighbour( m_xIndex - 1, m_yIndex ); + if( left != null ) left.contract(); + } + if( m_xIndex + 1 < m_width ) + { + TileMonitor right = getNeighbour( m_xIndex + 1, m_yIndex ); + if( right != null ) right.contract(); + } + if( m_yIndex > 0 ) + { + TileMonitor below = getNeighbour( m_xIndex, m_yIndex - 1 ); + if( below != null ) below.contract(); + } + if( m_yIndex + 1 < m_height ) + { + TileMonitor above = getNeighbour( m_xIndex, m_yIndex + 1 ); + if( above != null ) above.contract(); + } + visiting = false; + } + + void contract() + { + int height = m_height; + int width = m_width; + + TileMonitor origin = getOrigin(); + if( origin == null ) + { + TileMonitor right = width > 1 ? getNeighbour( 1, 0 ) : null; + TileMonitor below = height > 1 ? getNeighbour( 0, 1 ) : null; + + if( right != null ) right.resize( width - 1, 1 ); + if( below != null ) below.resize( width, height - 1 ); + if( right != null ) right.expand(); + if( below != null ) below.expand(); + + return; + } + + for( int y = 0; y < height; y++ ) + { + for( int x = 0; x < width; x++ ) + { + TileMonitor monitor = origin.getNeighbour( x, y ); + if( monitor != null ) continue; + + // Decompose + TileMonitor above = null; + TileMonitor left = null; + TileMonitor right = null; + TileMonitor below = null; + + if( y > 0 ) + { + above = origin; + above.resize( width, y ); + } + if( x > 0 ) + { + left = origin.getNeighbour( 0, y ); + left.resize( x, 1 ); + } + if( x + 1 < width ) + { + right = origin.getNeighbour( x + 1, y ); + right.resize( width - (x + 1), 1 ); + } + if( y + 1 < height ) + { + below = origin.getNeighbour( 0, y + 1 ); + below.resize( width, height - (y + 1) ); + } + + // Re-expand + if( above != null ) above.expand(); + if( left != null ) left.expand(); + if( right != null ) right.expand(); + if( below != null ) below.expand(); + return; + } + } + } + + private void monitorTouched( float xPos, float yPos, float zPos ) + { + XYPair pair = XYPair + .of( xPos, yPos, zPos, getDirection(), getOrientation() ) + .add( m_xIndex, m_height - m_yIndex - 1 ); + + if( pair.x > m_width - RENDER_BORDER || pair.y > m_height - RENDER_BORDER || pair.x < RENDER_BORDER || pair.y < RENDER_BORDER ) + { + return; + } + + ServerTerminal serverTerminal = getServerMonitor(); + if( serverTerminal == null || !serverTerminal.isColour() ) return; + + Terminal originTerminal = serverTerminal.getTerminal(); + if( originTerminal == null ) return; + + double xCharWidth = (m_width - (RENDER_BORDER + RENDER_MARGIN) * 2.0) / originTerminal.getWidth(); + double yCharHeight = (m_height - (RENDER_BORDER + RENDER_MARGIN) * 2.0) / originTerminal.getHeight(); + + int xCharPos = (int) Math.min( originTerminal.getWidth(), Math.max( (pair.x - RENDER_BORDER - RENDER_MARGIN) / xCharWidth + 1.0, 1.0 ) ); + int yCharPos = (int) Math.min( originTerminal.getHeight(), Math.max( (pair.y - RENDER_BORDER - RENDER_MARGIN) / yCharHeight + 1.0, 1.0 ) ); + + for( int y = 0; y < m_height; y++ ) + { + for( int x = 0; x < m_width; x++ ) + { + TileMonitor monitor = getNeighbour( x, y ); + if( monitor == null ) continue; + + for( IComputerAccess computer : monitor.m_computers ) + { + computer.queueEvent( "monitor_touch", new Object[] { + computer.getAttachmentName(), xCharPos, yCharPos + } ); + } + } + } + } + // endregion + + void addComputer( IComputerAccess computer ) + { + m_computers.add( computer ); + } + + void removeComputer( IComputerAccess computer ) + { + m_computers.remove( computer ); + } + + /* + @Nonnull + @Override + public AxisAlignedBB getRenderBoundingBox() + { + TileMonitor start = getNeighbour( 0, 0 ); + TileMonitor end = getNeighbour( width - 1, height - 1 ); + if( start != null && end != null ) + { + BlockPos startPos = start.getPos(); + BlockPos endPos = end.getPos(); + int minX = Math.min( startPos.getX(), endPos.getX() ); + int minY = Math.min( startPos.getY(), endPos.getY() ); + int minZ = Math.min( startPos.getZ(), endPos.getZ() ); + int maxX = Math.max( startPos.getX(), endPos.getX() ) + 1; + int maxY = Math.max( startPos.getY(), endPos.getY() ) + 1; + int maxZ = Math.max( startPos.getZ(), endPos.getZ() ) + 1; + return new AxisAlignedBB( minX, minY, minZ, maxX, maxY, maxZ ); + } + else + { + BlockPos pos = getPos(); + return new AxisAlignedBB( pos.getX(), pos.getY(), pos.getZ(), pos.getX() + 1, pos.getY() + 1, pos.getZ() + 1 ); + } + } + */ +} diff --git a/remappedSrc/dan200/computercraft/shared/peripheral/monitor/XYPair.java b/remappedSrc/dan200/computercraft/shared/peripheral/monitor/XYPair.java new file mode 100644 index 000000000..29d6a3279 --- /dev/null +++ b/remappedSrc/dan200/computercraft/shared/peripheral/monitor/XYPair.java @@ -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.shared.peripheral.monitor; + +import net.minecraft.util.math.Direction; + +public class XYPair +{ + public final float x; + public final float y; + + public XYPair( float x, float y ) + { + this.x = x; + this.y = y; + } + + public XYPair add( float x, float y ) + { + return new XYPair( this.x + x, this.y + y ); + } + + public static XYPair of( float xPos, float yPos, float zPos, Direction facing, Direction orientation ) + { + switch( orientation ) + { + case NORTH: + switch( facing ) + { + case NORTH: + return new XYPair( 1 - xPos, 1 - yPos ); + case SOUTH: + return new XYPair( xPos, 1 - yPos ); + case WEST: + return new XYPair( zPos, 1 - yPos ); + case EAST: + return new XYPair( 1 - zPos, 1 - yPos ); + } + break; + case DOWN: + switch( facing ) + { + case NORTH: + return new XYPair( 1 - xPos, zPos ); + case SOUTH: + return new XYPair( xPos, 1 - zPos ); + case WEST: + return new XYPair( zPos, xPos ); + case EAST: + return new XYPair( 1 - zPos, 1 - xPos ); + } + break; + case UP: + switch( facing ) + { + case NORTH: + return new XYPair( 1 - xPos, 1 - zPos ); + case SOUTH: + return new XYPair( xPos, zPos ); + case WEST: + return new XYPair( zPos, 1 - xPos ); + case EAST: + return new XYPair( 1 - zPos, xPos ); + } + break; + } + + return new XYPair( xPos, zPos ); + } +} diff --git a/remappedSrc/dan200/computercraft/shared/peripheral/printer/BlockPrinter.java b/remappedSrc/dan200/computercraft/shared/peripheral/printer/BlockPrinter.java new file mode 100644 index 000000000..ed7aff55d --- /dev/null +++ b/remappedSrc/dan200/computercraft/shared/peripheral/printer/BlockPrinter.java @@ -0,0 +1,64 @@ +/* + * 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.shared.peripheral.printer; + +import dan200.computercraft.shared.common.BlockGeneric; +import net.minecraft.block.Block; +import net.minecraft.block.BlockState; +import net.minecraft.block.entity.BlockEntity; +import net.minecraft.entity.LivingEntity; +import net.minecraft.item.ItemPlacementContext; +import net.minecraft.item.ItemStack; +import net.minecraft.state.StateManager; +import net.minecraft.state.property.BooleanProperty; +import net.minecraft.state.property.DirectionProperty; +import net.minecraft.state.property.Properties; +import net.minecraft.util.math.BlockPos; +import net.minecraft.util.math.Direction; +import net.minecraft.world.World; + +import javax.annotation.Nullable; + +public class BlockPrinter extends BlockGeneric +{ + private static final DirectionProperty FACING = Properties.HORIZONTAL_FACING; + static final BooleanProperty TOP = BooleanProperty.of( "top" ); + static final BooleanProperty BOTTOM = BooleanProperty.of( "bottom" ); + + public BlockPrinter( Settings settings ) + { + super( settings, TilePrinter.FACTORY ); + setDefaultState( getStateManager().getDefaultState() + .with( FACING, Direction.NORTH ) + .with( TOP, false ) + .with( BOTTOM, false ) ); + } + + @Override + protected void appendProperties( StateManager.Builder properties ) + { + super.appendProperties( properties ); + properties.add( FACING, TOP, BOTTOM ); + } + + @Nullable + @Override + public BlockState getPlacementState( ItemPlacementContext placement ) + { + return getDefaultState().with( FACING, placement.getPlayerFacing().getOpposite() ); + } + + @Override + public void onPlaced( World world, BlockPos pos, BlockState state, LivingEntity placer, ItemStack stack ) + { + if( stack.hasCustomName() ) + { + BlockEntity tileentity = world.getBlockEntity( pos ); + if( tileentity instanceof TilePrinter ) ((TilePrinter) tileentity).customName = stack.getName(); + } + } +} diff --git a/remappedSrc/dan200/computercraft/shared/peripheral/printer/ContainerPrinter.java b/remappedSrc/dan200/computercraft/shared/peripheral/printer/ContainerPrinter.java new file mode 100644 index 000000000..19558dacc --- /dev/null +++ b/remappedSrc/dan200/computercraft/shared/peripheral/printer/ContainerPrinter.java @@ -0,0 +1,119 @@ +/* + * 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.shared.peripheral.printer; + +import net.minecraft.entity.player.PlayerEntity; +import net.minecraft.entity.player.PlayerInventory; +import net.minecraft.inventory.Inventory; +import net.minecraft.inventory.SimpleInventory; +import net.minecraft.item.DyeItem; +import net.minecraft.item.ItemStack; +import net.minecraft.screen.ArrayPropertyDelegate; +import net.minecraft.screen.PropertyDelegate; +import net.minecraft.screen.ScreenHandler; +import net.minecraft.screen.slot.Slot; +import javax.annotation.Nonnull; + +import static dan200.computercraft.shared.peripheral.printer.TilePrinter.PROPERTY_PRINTING; + +public class ContainerPrinter extends ScreenHandler +{ + private final Inventory m_printer; + private final PropertyDelegate properties; + + public ContainerPrinter( int id, PlayerInventory player, TilePrinter printer ) + { + this( id, player, printer, printer ); + } + + public ContainerPrinter( int id, PlayerInventory player ) + { + this( id, player, new SimpleInventory( TilePrinter.INVENTORY_SIZE ), new ArrayPropertyDelegate( TilePrinter.PROPERTY_SIZE ) ); + } + + public ContainerPrinter( int id, PlayerInventory playerInventory, Inventory printer, PropertyDelegate printerInfo ) + { + super( null, id ); + m_printer = printer; + properties = printerInfo; + + // Ink slot + addSlot( new Slot( printer, 0, 13, 35 ) ); + + // In-tray + for( int x = 0; x < 6; x++ ) addSlot( new Slot( printer, x + 1, 61 + x * 18, 22 ) ); + + // Out-tray + for( int x = 0; x < 6; x++ ) addSlot( new Slot( printer, x + 7, 61 + x * 18, 49 ) ); + + // Player inv + for( int y = 0; y < 3; y++ ) + { + for( int x = 0; x < 9; x++ ) + { + addSlot( new Slot( playerInventory, x + y * 9 + 9, 8 + x * 18, 84 + y * 18 ) ); + } + } + + // Player hotbar + for( int x = 0; x < 9; x++ ) addSlot( new Slot( playerInventory, x, 8 + x * 18, 142 ) ); + + addProperties( printerInfo ); + } + + public boolean isPrinting() + { + return properties.get( PROPERTY_PRINTING ) != 0; + } + + @Override + public boolean canUse( @Nonnull PlayerEntity player ) + { + return m_printer.canPlayerUse( player ); + } + + @Nonnull + @Override + public ItemStack transferSlot( PlayerEntity player, int index ) + { + Slot slot = slots.get( index ); + if( slot == null || !slot.hasStack() ) return ItemStack.EMPTY; + ItemStack stack = slot.getStack(); + ItemStack result = stack.copy(); + if( index < 13 ) + { + // Transfer from printer to inventory + if( !insertItem( stack, 13, 49, true ) ) return ItemStack.EMPTY; + } + else + { + // Transfer from inventory to printer + if( stack.getItem() instanceof DyeItem ) + { + if( !insertItem( stack, 0, 1, false ) ) return ItemStack.EMPTY; + } + else //if is paper + { + if( !insertItem( stack, 1, 13, false ) ) return ItemStack.EMPTY; + } + } + + if( stack.isEmpty() ) + { + slot.setStack( ItemStack.EMPTY ); + } + else + { + slot.markDirty(); + } + + if( stack.getCount() == result.getCount() ) return ItemStack.EMPTY; + + slot.onTakeItem( player, stack ); + return result; + } +} diff --git a/remappedSrc/dan200/computercraft/shared/peripheral/printer/PrinterPeripheral.java b/remappedSrc/dan200/computercraft/shared/peripheral/printer/PrinterPeripheral.java new file mode 100644 index 000000000..0a786b1e2 --- /dev/null +++ b/remappedSrc/dan200/computercraft/shared/peripheral/printer/PrinterPeripheral.java @@ -0,0 +1,135 @@ +/* + * 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.shared.peripheral.printer; + +import dan200.computercraft.api.lua.ILuaContext; +import dan200.computercraft.api.lua.LuaException; +import dan200.computercraft.api.peripheral.IComputerAccess; +import dan200.computercraft.api.peripheral.IPeripheral; +import dan200.computercraft.core.terminal.Terminal; + +import javax.annotation.Nonnull; + +import static dan200.computercraft.core.apis.ArgumentHelper.getInt; +import static dan200.computercraft.core.apis.ArgumentHelper.optString; + +public class PrinterPeripheral implements IPeripheral +{ + private final TilePrinter m_printer; + + public PrinterPeripheral( TilePrinter printer ) + { + m_printer = printer; + } + + @Nonnull + @Override + public String getType() + { + return "printer"; + } + + @Nonnull + @Override + public String[] getMethodNames() + { + return new String[] { + "write", + "setCursorPos", + "getCursorPos", + "getPageSize", + "newPage", + "endPage", + "getInkLevel", + "setPageTitle", + "getPaperLevel", + }; + } + + @Override + public Object[] callMethod( @Nonnull IComputerAccess computer, @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() : ""; + Terminal page = getCurrentPage(); + page.write( text ); + page.setCursorPos( page.getCursorX() + text.length(), page.getCursorY() ); + return null; + } + case 1: + { + // setCursorPos + int x = getInt( args, 0 ) - 1; + int y = getInt( args, 1 ) - 1; + Terminal page = getCurrentPage(); + page.setCursorPos( x, y ); + return null; + } + case 2: + { + // getCursorPos + Terminal page = getCurrentPage(); + int x = page.getCursorX(); + int y = page.getCursorY(); + return new Object[] { x + 1, y + 1 }; + } + case 3: + { + // getPageSize + Terminal page = getCurrentPage(); + int width = page.getWidth(); + int height = page.getHeight(); + return new Object[] { width, height }; + } + case 4: // newPage + return new Object[] { m_printer.startNewPage() }; + case 5: // endPage + getCurrentPage(); + return new Object[] { m_printer.endCurrentPage() }; + case 6: // getInkLevel + return new Object[] { m_printer.getInkLevel() }; + case 7: + { + // setPageTitle + String title = optString( args, 0, "" ); + getCurrentPage(); + m_printer.setPageTitle( title ); + return null; + } + case 8: // getPaperLevel + return new Object[] { m_printer.getPaperLevel() }; + default: + return null; + } + } + + @Override + public boolean equals( IPeripheral other ) + { + return other instanceof PrinterPeripheral && ((PrinterPeripheral) other).m_printer == m_printer; + } + + @Nonnull + @Override + public Object getTarget() + { + return m_printer; + } + + private Terminal getCurrentPage() throws LuaException + { + Terminal currentPage = m_printer.getCurrentPage(); + if( currentPage == null ) + { + throw new LuaException( "Page not started" ); + } + return currentPage; + } +} diff --git a/remappedSrc/dan200/computercraft/shared/peripheral/printer/TilePrinter.java b/remappedSrc/dan200/computercraft/shared/peripheral/printer/TilePrinter.java new file mode 100644 index 000000000..a863fe4a3 --- /dev/null +++ b/remappedSrc/dan200/computercraft/shared/peripheral/printer/TilePrinter.java @@ -0,0 +1,544 @@ +/* + * 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.shared.peripheral.printer; + +import dan200.computercraft.ComputerCraft; +import dan200.computercraft.api.peripheral.IPeripheral; +import dan200.computercraft.api.peripheral.IPeripheralTile; +import dan200.computercraft.core.terminal.Terminal; +import dan200.computercraft.shared.common.TileGeneric; +import dan200.computercraft.shared.media.items.ItemPrintout; +import dan200.computercraft.shared.network.Containers; +import dan200.computercraft.shared.util.*; +import net.minecraft.block.BlockState; +import net.minecraft.entity.player.PlayerEntity; +import net.minecraft.inventory.Inventories; +import net.minecraft.item.DyeItem; +import net.minecraft.item.Item; +import net.minecraft.item.ItemStack; +import net.minecraft.item.Items; +import net.minecraft.nbt.CompoundTag; +import net.minecraft.text.LiteralText; +import net.minecraft.text.Text; +import net.minecraft.util.*; +import net.minecraft.util.collection.DefaultedList; +import net.minecraft.util.hit.BlockHitResult; +import net.minecraft.util.math.BlockPos; +import net.minecraft.util.math.Direction; + +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public final class TilePrinter extends TileGeneric implements DefaultSidedInventory, IPeripheralTile, DefaultPropertyDelegate, Nameable +{ + public static final NamedBlockEntityType FACTORY = NamedBlockEntityType.create( + new Identifier( ComputerCraft.MOD_ID, "printer" ), + TilePrinter::new + ); + + private static final String NBT_NAME = "CustomName"; + private static final String NBT_PRINTING = "Printing"; + private static final String NBT_PAGE_TITLE = "PageTitle"; + + public static final int PROPERTY_SIZE = 1; + public static final int PROPERTY_PRINTING = 0; + + public static final int INVENTORY_SIZE = 13; + + private static final int[] BOTTOM_SLOTS = new int[] { 7, 8, 9, 10, 11, 12 }; + private static final int[] TOP_SLOTS = new int[] { 1, 2, 3, 4, 5, 6 }; + private static final int[] SIDE_SLOTS = new int[] { 0 }; + + Text customName; + + private final DefaultedList m_inventory = DefaultedList.ofSize( INVENTORY_SIZE, ItemStack.EMPTY ); + private final ItemStorage m_itemHandlerAll = ItemStorage.wrap( this ); + + private final Terminal m_page = new Terminal( ItemPrintout.LINE_MAX_LENGTH, ItemPrintout.LINES_PER_PAGE ); + private String m_pageTitle = ""; + private boolean m_printing = false; + + private TilePrinter() + { + super( FACTORY ); + } + + @Override + public void destroy() + { + ejectContents(); + } + + @Override + public boolean onActivate( PlayerEntity player, Hand hand, BlockHitResult hit ) + { + if( player.isSneaking() ) return false; + + if( !getWorld().isClient ) Containers.openPrinterGUI( player, this ); + return true; + } + + @Override + public void fromTag( CompoundTag nbt ) + { + super.fromTag( nbt ); + + customName = nbt.contains( NBT_NAME ) ? LiteralText.Serializer.fromJson( nbt.getString( NBT_NAME ) ) : null; + + // Read page + synchronized( m_page ) + { + m_printing = nbt.getBoolean( NBT_PRINTING ); + m_pageTitle = nbt.getString( NBT_PAGE_TITLE ); + m_page.readFromNBT( nbt ); + } + + // Read inventory + synchronized( m_inventory ) + { + Inventories.fromTag( nbt, m_inventory ); + } + } + + @Nonnull + @Override + public CompoundTag toTag( CompoundTag nbt ) + { + if( customName != null ) nbt.putString( NBT_NAME, LiteralText.Serializer.toJson( customName ) ); + + // Write page + synchronized( m_page ) + { + nbt.putBoolean( NBT_PRINTING, m_printing ); + nbt.putString( NBT_PAGE_TITLE, m_pageTitle ); + m_page.writeToNBT( nbt ); + } + + // Write inventory + synchronized( m_inventory ) + { + Inventories.toTag( nbt, m_inventory ); + } + + return super.toTag( nbt ); + } + + @Override + protected void writeDescription( @Nonnull CompoundTag nbt ) + { + super.writeDescription( nbt ); + if( customName != null ) nbt.putString( NBT_NAME, LiteralText.Serializer.toJson( customName ) ); + } + + @Override + public void readDescription( @Nonnull CompoundTag nbt ) + { + super.readDescription( nbt ); + customName = nbt.contains( NBT_NAME ) ? LiteralText.Serializer.fromJson( nbt.getString( NBT_NAME ) ) : null; + updateBlock(); + } + + public boolean isPrinting() + { + return m_printing; + } + + // Inventory implementation + @Override + public int size() + { + return m_inventory.size(); + } + + @Override + public boolean isEmpty() + { + for( ItemStack stack : m_inventory ) + { + if( !stack.isEmpty() ) return false; + } + return true; + } + + @Nonnull + @Override + public ItemStack getStack( int i ) + { + return m_inventory.get( i ); + } + + @Nonnull + @Override + public ItemStack removeStack( int i ) + { + synchronized( m_inventory ) + { + ItemStack result = m_inventory.get( i ); + m_inventory.set( i, ItemStack.EMPTY ); + markDirty(); + updateBlockState(); + return result; + } + } + + @Nonnull + @Override + public ItemStack removeStack( int i, int j ) + { + synchronized( m_inventory ) + { + if( m_inventory.get( i ).isEmpty() ) return ItemStack.EMPTY; + + if( m_inventory.get( i ).getCount() <= j ) + { + ItemStack itemstack = m_inventory.get( i ); + m_inventory.set( i, ItemStack.EMPTY ); + markDirty(); + updateBlockState(); + return itemstack; + } + + ItemStack part = m_inventory.get( i ).split( j ); + if( m_inventory.get( i ).isEmpty() ) + { + m_inventory.set( i, ItemStack.EMPTY ); + updateBlockState(); + } + markDirty(); + return part; + } + } + + @Override + public void setStack( int i, @Nonnull ItemStack stack ) + { + synchronized( m_inventory ) + { + m_inventory.set( i, stack ); + markDirty(); + updateBlockState(); + } + } + + @Override + public void clear() + { + synchronized( m_inventory ) + { + for( int i = 0; i < m_inventory.size(); i++ ) m_inventory.set( i, ItemStack.EMPTY ); + markDirty(); + updateBlockState(); + } + } + + @Override + public boolean isValid( int slot, @Nonnull ItemStack stack ) + { + if( slot == 0 ) + { + return isInk( stack ); + } + else if( slot >= TOP_SLOTS[0] && slot <= TOP_SLOTS[TOP_SLOTS.length - 1] ) + { + return isPaper( stack ); + } + else + { + return false; + } + } + + @Override + public boolean canPlayerUse( PlayerEntity playerEntity ) + { + return isUsable( playerEntity, false ); + } + + // ISidedInventory implementation + + @Override + public int[] getAvailableSlots( @Nonnull Direction side ) + { + switch( side ) + { + case DOWN: // Bottom (Out tray) + return BOTTOM_SLOTS; + case UP: // Top (In tray) + return TOP_SLOTS; + default: // Sides (Ink) + return SIDE_SLOTS; + } + } + + // IPeripheralTile implementation + + @Override + public IPeripheral getPeripheral( @Nonnull Direction side ) + { + return new PrinterPeripheral( this ); + } + + public Terminal getCurrentPage() + { + return m_printing ? m_page : null; + } + + public boolean startNewPage() + { + synchronized( m_inventory ) + { + if( !canInputPage() ) return false; + if( m_printing && !outputPage() ) return false; + return inputPage(); + } + } + + public boolean endCurrentPage() + { + synchronized( m_inventory ) + { + if( m_printing && outputPage() ) + { + return true; + } + } + return false; + } + + public int getInkLevel() + { + synchronized( m_inventory ) + { + ItemStack inkStack = m_inventory.get( 0 ); + return isInk( inkStack ) ? inkStack.getCount() : 0; + } + } + + public int getPaperLevel() + { + int count = 0; + synchronized( m_inventory ) + { + for( int i = 1; i < 7; i++ ) + { + ItemStack paperStack = m_inventory.get( i ); + if( !paperStack.isEmpty() && isPaper( paperStack ) ) + { + count += paperStack.getCount(); + } + } + } + return count; + } + + public void setPageTitle( String title ) + { + if( m_printing ) + { + m_pageTitle = title; + } + } + + private static boolean isInk( @Nonnull ItemStack stack ) + { + return stack.getItem() instanceof DyeItem; + } + + private static boolean isPaper( @Nonnull ItemStack stack ) + { + Item item = stack.getItem(); + return item == Items.PAPER + || (item instanceof ItemPrintout && ((ItemPrintout) item).getType() == ItemPrintout.Type.PAGE); + } + + private boolean canInputPage() + { + synchronized( m_inventory ) + { + ItemStack inkStack = m_inventory.get( 0 ); + return !inkStack.isEmpty() && isInk( inkStack ) && getPaperLevel() > 0; + } + } + + private boolean inputPage() + { + synchronized( m_inventory ) + { + ItemStack inkStack = m_inventory.get( 0 ); + if( !isInk( inkStack ) ) return false; + + for( int i = 1; i < 7; i++ ) + { + ItemStack paperStack = m_inventory.get( i ); + if( !paperStack.isEmpty() && isPaper( paperStack ) ) + { + // Setup the new page + DyeColor dye = ColourUtils.getStackColour( inkStack ); + m_page.setTextColour( dye != null ? dye.getId() : 15 ); + + m_page.clear(); + if( paperStack.getItem() instanceof ItemPrintout ) + { + m_pageTitle = ItemPrintout.getTitle( paperStack ); + String[] text = ItemPrintout.getText( paperStack ); + String[] textColour = ItemPrintout.getColours( paperStack ); + for( int y = 0; y < m_page.getHeight(); y++ ) + { + m_page.setLine( y, text[y], textColour[y], "" ); + } + } + else + { + m_pageTitle = ""; + } + m_page.setCursorPos( 0, 0 ); + + // Decrement ink + inkStack.decrement( 1 ); + if( inkStack.isEmpty() ) m_inventory.set( 0, ItemStack.EMPTY ); + + // Decrement paper + paperStack.decrement( 1 ); + if( paperStack.isEmpty() ) + { + m_inventory.set( i, ItemStack.EMPTY ); + updateBlockState(); + } + + markDirty(); + m_printing = true; + return true; + } + } + return false; + } + } + + private boolean outputPage() + { + synchronized( m_page ) + { + int height = m_page.getHeight(); + String[] lines = new String[height]; + String[] colours = new String[height]; + for( int i = 0; i < height; i++ ) + { + lines[i] = m_page.getLine( i ).toString(); + colours[i] = m_page.getTextColourLine( i ).toString(); + } + + ItemStack stack = ItemPrintout.createSingleFromTitleAndText( m_pageTitle, lines, colours ); + synchronized( m_inventory ) + { + for( int slot : BOTTOM_SLOTS ) + { + if( m_inventory.get( slot ).isEmpty() ) + { + setStack( slot, stack ); + m_printing = false; + return true; + } + } + } + return false; + } + } + + private void ejectContents() + { + synchronized( m_inventory ) + { + for( int i = 0; i < 13; i++ ) + { + ItemStack stack = m_inventory.get( i ); + if( !stack.isEmpty() ) + { + // Remove the stack from the inventory + setStack( i, ItemStack.EMPTY ); + + // Spawn the item in the world + BlockPos pos = getPos(); + double x = pos.getX() + 0.5; + double y = pos.getY() + 0.75; + double z = pos.getZ() + 0.5; + WorldUtil.dropItemStack( stack, getWorld(), x, y, z ); + } + } + } + } + + private void updateBlockState() + { + boolean top = false, bottom = false; + synchronized( m_inventory ) + { + for( int i = 1; i < 7; i++ ) + { + ItemStack stack = m_inventory.get( i ); + if( !stack.isEmpty() && isPaper( stack ) ) + { + top = true; + break; + } + } + for( int i = 7; i < 13; i++ ) + { + ItemStack stack = m_inventory.get( i ); + if( !stack.isEmpty() && isPaper( stack ) ) + { + bottom = true; + break; + } + } + } + + updateBlockState( top, bottom ); + } + + private void updateBlockState( boolean top, boolean bottom ) + { + if( removed ) return; + + BlockState state = getCachedState(); + if( state.get( BlockPrinter.TOP ) == top & state.get( BlockPrinter.BOTTOM ) == bottom ) return; + + getWorld().setBlockState( getPos(), state.with( BlockPrinter.TOP, top ).with( BlockPrinter.BOTTOM, bottom ) ); + } + + @Override + public int get( int property ) + { + if( property == PROPERTY_PRINTING ) return isPrinting() ? 1 : 0; + return 0; + } + + @Override + public int size() + { + return PROPERTY_SIZE; + } + + @Override + public boolean hasCustomName() + { + return customName != null; + } + + @Nullable + @Override + public Text getCustomName() + { + return customName; + } + + @Nonnull + @Override + public Text getName() + { + return customName != null ? customName : getCachedState().getBlock().getName(); + } +} diff --git a/remappedSrc/dan200/computercraft/shared/peripheral/speaker/BlockSpeaker.java b/remappedSrc/dan200/computercraft/shared/peripheral/speaker/BlockSpeaker.java new file mode 100644 index 000000000..790f91c1d --- /dev/null +++ b/remappedSrc/dan200/computercraft/shared/peripheral/speaker/BlockSpeaker.java @@ -0,0 +1,43 @@ +/* + * 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.shared.peripheral.speaker; + +import dan200.computercraft.shared.common.BlockGeneric; +import net.minecraft.block.Block; +import net.minecraft.block.BlockState; +import net.minecraft.item.ItemPlacementContext; +import net.minecraft.state.StateManager; +import net.minecraft.state.property.DirectionProperty; +import net.minecraft.state.property.Properties; +import net.minecraft.util.math.Direction; + +import javax.annotation.Nullable; + +public class BlockSpeaker extends BlockGeneric +{ + private static final DirectionProperty FACING = Properties.HORIZONTAL_FACING; + + public BlockSpeaker( Settings settings ) + { + super( settings, TileSpeaker.FACTORY ); + setDefaultState( getStateManager().getDefaultState() + .with( FACING, Direction.NORTH ) ); + } + + @Override + protected void appendProperties( StateManager.Builder properties ) + { + properties.add( FACING ); + } + + @Nullable + @Override + public BlockState getPlacementState( ItemPlacementContext placement ) + { + return getDefaultState().with( FACING, placement.getPlayerFacing().getOpposite() ); + } +} diff --git a/remappedSrc/dan200/computercraft/shared/peripheral/speaker/SpeakerPeripheral.java b/remappedSrc/dan200/computercraft/shared/peripheral/speaker/SpeakerPeripheral.java new file mode 100644 index 000000000..d5e094ab3 --- /dev/null +++ b/remappedSrc/dan200/computercraft/shared/peripheral/speaker/SpeakerPeripheral.java @@ -0,0 +1,158 @@ +/* + * 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.shared.peripheral.speaker; + +import dan200.computercraft.ComputerCraft; +import dan200.computercraft.api.lua.ILuaContext; +import dan200.computercraft.api.lua.LuaException; +import dan200.computercraft.api.peripheral.IComputerAccess; +import dan200.computercraft.api.peripheral.IPeripheral; +import net.minecraft.block.enums.Instrument; +import net.minecraft.network.packet.s2c.play.PlaySoundIdS2CPacket; +import net.minecraft.server.MinecraftServer; +import net.minecraft.sound.SoundCategory; +import net.minecraft.util.Identifier; +import net.minecraft.util.InvalidIdentifierException; +import net.minecraft.util.math.Vec3d; +import net.minecraft.world.World; + +import javax.annotation.Nonnull; +import java.util.concurrent.atomic.AtomicInteger; + +import static dan200.computercraft.core.apis.ArgumentHelper.getString; +import static dan200.computercraft.core.apis.ArgumentHelper.optReal; + +public abstract class SpeakerPeripheral implements IPeripheral +{ + private long m_clock = 0; + private long m_lastPlayTime = 0; + private final AtomicInteger m_notesThisTick = new AtomicInteger(); + + public void update() + { + m_clock++; + m_notesThisTick.set( 0 ); + } + + public abstract World getWorld(); + + public abstract Vec3d getPosition(); + + public boolean madeSound( long ticks ) + { + return m_clock - m_lastPlayTime <= ticks; + } + + @Nonnull + @Override + public String getType() + { + return "speaker"; + } + + @Nonnull + @Override + public String[] getMethodNames() + { + return new String[] { + "playSound", // Plays sound at resourceLocator + "playNote" // Plays note + }; + } + + @Override + public Object[] callMethod( @Nonnull IComputerAccess computerAccess, @Nonnull ILuaContext context, int methodIndex, @Nonnull Object[] args ) throws LuaException + { + switch( methodIndex ) + { + case 0: // playSound + { + String name = getString( args, 0 ); + float volume = (float) optReal( args, 1, 1.0 ); + float pitch = (float) optReal( args, 2, 1.0 ); + + Identifier identifier; + try + { + identifier = new Identifier( name ); + } + catch( InvalidIdentifierException e ) + { + throw new LuaException( "Malformed sound name '" + name + "' " ); + } + + return new Object[] { playSound( context, identifier, volume, pitch, false ) }; + } + + case 1: // playNote + return playNote( args, context ); + + default: + throw new IllegalStateException( "Method index out of range!" ); + } + } + + @Nonnull + private synchronized Object[] playNote( Object[] arguments, ILuaContext context ) throws LuaException + { + String name = getString( arguments, 0 ); + float volume = (float) optReal( arguments, 1, 1.0 ); + float pitch = (float) optReal( arguments, 2, 1.0 ); + + Instrument instrument = null; + for( Instrument testInstrument : Instrument.values() ) + { + if( testInstrument.asString().equalsIgnoreCase( name ) ) + { + instrument = testInstrument; + break; + } + } + + // Check if the note exists + if( instrument == null ) + { + throw new LuaException( "Invalid instrument, \"" + name + "\"!" ); + } + + // If the resource location for note block notes changes, this method call will need to be updated + boolean success = playSound( context, instrument.getSound().getId(), volume, (float) Math.pow( 2.0, (pitch - 12.0) / 12.0 ), true ); + + if( success ) m_notesThisTick.incrementAndGet(); + return new Object[] { success }; + } + + private synchronized boolean playSound( ILuaContext context, Identifier name, float volume, float pitch, boolean isNote ) throws LuaException + { + if( m_clock - m_lastPlayTime < TileSpeaker.MIN_TICKS_BETWEEN_SOUNDS && + (!isNote || m_clock - m_lastPlayTime != 0 || m_notesThisTick.get() >= ComputerCraft.maxNotesPerTick) ) + { + // Rate limiting occurs when we've already played a sound within the last tick, or we've + // played more notes than allowable within the current tick. + return false; + } + + World world = getWorld(); + Vec3d pos = getPosition(); + + context.issueMainThreadTask( () -> { + MinecraftServer server = world.getServer(); + if( server == null ) return null; + + float adjVolume = Math.min( volume, 3.0f ); + server.getPlayerManager().sendToAround( + null, pos.x, pos.y, pos.z, adjVolume > 1.0f ? 16 * adjVolume : 16.0, world.getDimension().getType(), + new PlaySoundIdS2CPacket( name, SoundCategory.RECORDS, pos, adjVolume, pitch ) + ); + return null; + } ); + + m_lastPlayTime = m_clock; + return true; + } +} + diff --git a/remappedSrc/dan200/computercraft/shared/peripheral/speaker/TileSpeaker.java b/remappedSrc/dan200/computercraft/shared/peripheral/speaker/TileSpeaker.java new file mode 100644 index 000000000..b1c34de99 --- /dev/null +++ b/remappedSrc/dan200/computercraft/shared/peripheral/speaker/TileSpeaker.java @@ -0,0 +1,82 @@ +/* + * 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.shared.peripheral.speaker; + +import dan200.computercraft.ComputerCraft; +import dan200.computercraft.api.peripheral.IPeripheral; +import dan200.computercraft.api.peripheral.IPeripheralTile; +import dan200.computercraft.shared.common.TileGeneric; +import dan200.computercraft.shared.util.NamedBlockEntityType; +import net.minecraft.util.Identifier; +import net.minecraft.util.Tickable; +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; + +public class TileSpeaker extends TileGeneric implements Tickable, IPeripheralTile +{ + public static final int MIN_TICKS_BETWEEN_SOUNDS = 1; + + public static final NamedBlockEntityType FACTORY = NamedBlockEntityType.create( + new Identifier( ComputerCraft.MOD_ID, "speaker" ), + TileSpeaker::new + ); + + private final SpeakerPeripheral m_peripheral; + + public TileSpeaker() + { + super( FACTORY ); + m_peripheral = new Peripheral( this ); + } + + @Override + public void tick() + { + m_peripheral.update(); + } + + @Override + public IPeripheral getPeripheral( @Nonnull Direction side ) + { + return m_peripheral; + } + + private static final class Peripheral extends SpeakerPeripheral + { + private final TileSpeaker speaker; + + private Peripheral( TileSpeaker speaker ) + { + this.speaker = speaker; + } + + @Override + public World getWorld() + { + return speaker.getWorld(); + } + + @Override + public Vec3d getPosition() + { + BlockPos pos = speaker.getPos(); + return new Vec3d( pos.getX(), pos.getY(), pos.getZ() ); + } + + @Override + public boolean equals( @Nullable IPeripheral other ) + { + return this == other || (other instanceof Peripheral && speaker == ((Peripheral) other).speaker); + } + } +} + diff --git a/remappedSrc/dan200/computercraft/shared/pocket/apis/PocketAPI.java b/remappedSrc/dan200/computercraft/shared/pocket/apis/PocketAPI.java new file mode 100644 index 000000000..768e072d1 --- /dev/null +++ b/remappedSrc/dan200/computercraft/shared/pocket/apis/PocketAPI.java @@ -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.shared.pocket.apis; + +import dan200.computercraft.api.lua.ILuaAPI; +import dan200.computercraft.api.lua.ILuaContext; +import dan200.computercraft.api.lua.LuaException; +import dan200.computercraft.api.pocket.IPocketUpgrade; +import dan200.computercraft.shared.PocketUpgrades; +import dan200.computercraft.shared.pocket.core.PocketServerComputer; +import dan200.computercraft.shared.util.InventoryUtil; +import dan200.computercraft.shared.util.ItemStorage; +import dan200.computercraft.shared.util.WorldUtil; +import net.minecraft.entity.Entity; +import net.minecraft.entity.player.PlayerEntity; +import net.minecraft.entity.player.PlayerInventory; +import net.minecraft.item.ItemStack; +import net.minecraft.util.collection.DefaultedList; +import javax.annotation.Nonnull; + +public class PocketAPI implements ILuaAPI +{ + private final PocketServerComputer m_computer; + + public PocketAPI( PocketServerComputer computer ) + { + m_computer = computer; + } + + @Override + public String[] getNames() + { + return new String[] { + "pocket" + }; + } + + @Nonnull + @Override + public String[] getMethodNames() + { + return new String[] { + "equipBack", + "unequipBack" + }; + } + + @Override + public Object[] callMethod( @Nonnull ILuaContext context, int method, @Nonnull Object[] arguments ) throws LuaException, InterruptedException + { + switch( method ) + { + case 0: + // equipBack + return context.executeMainThreadTask( () -> + { + Entity entity = m_computer.getEntity(); + if( !(entity instanceof PlayerEntity) ) return new Object[] { false, "Cannot find player" }; + PlayerEntity player = (PlayerEntity) entity; + PlayerInventory inventory = player.inventory; + IPocketUpgrade previousUpgrade = m_computer.getUpgrade(); + + // Attempt to find the upgrade, starting in the main segment, and then looking in the opposite + // one. We start from the position the item is currently in and loop round to the start. + IPocketUpgrade newUpgrade = findUpgrade( inventory.main, inventory.selectedSlot, previousUpgrade ); + if( newUpgrade == null ) + { + newUpgrade = findUpgrade( inventory.offHand, 0, previousUpgrade ); + } + if( newUpgrade == null ) return new Object[] { false, "Cannot find a valid upgrade" }; + + // Remove the current upgrade + if( previousUpgrade != null ) + { + ItemStack stack = previousUpgrade.getCraftingItem(); + if( !stack.isEmpty() ) + { + stack = InventoryUtil.storeItems( stack, ItemStorage.wrap( inventory ).view( 0, 36 ), inventory.selectedSlot ); + if( !stack.isEmpty() ) + { + WorldUtil.dropItemStack( stack, player.getEntityWorld(), player.x, player.y, player.z ); + } + } + } + + // Set the new upgrade + m_computer.setUpgrade( newUpgrade ); + + return new Object[] { true }; + } ); + + case 1: + // unequipBack + return context.executeMainThreadTask( () -> + { + Entity entity = m_computer.getEntity(); + if( !(entity instanceof PlayerEntity) ) return new Object[] { false, "Cannot find player" }; + PlayerEntity player = (PlayerEntity) entity; + PlayerInventory inventory = player.inventory; + IPocketUpgrade previousUpgrade = m_computer.getUpgrade(); + + if( previousUpgrade == null ) return new Object[] { false, "Nothing to unequip" }; + + m_computer.setUpgrade( null ); + + ItemStack stack = previousUpgrade.getCraftingItem(); + if( !stack.isEmpty() ) + { + stack = InventoryUtil.storeItems( stack, ItemStorage.wrap( inventory ).view( 0, 36 ), inventory.selectedSlot ); + if( stack.isEmpty() ) + { + WorldUtil.dropItemStack( stack, player.getEntityWorld(), player.x, player.y, player.z ); + } + } + + return new Object[] { true }; + } ); + default: + return null; + } + } + + private static IPocketUpgrade findUpgrade( DefaultedList inv, int start, IPocketUpgrade previous ) + { + for( int i = 0; i < inv.size(); i++ ) + { + ItemStack invStack = inv.get( (i + start) % inv.size() ); + if( !invStack.isEmpty() ) + { + IPocketUpgrade newUpgrade = PocketUpgrades.get( invStack ); + + if( newUpgrade != null && newUpgrade != previous ) + { + // Consume an item from this stack and exit the loop + invStack = invStack.copy(); + invStack.decrement( 1 ); + inv.set( (i + start) % inv.size(), invStack.isEmpty() ? ItemStack.EMPTY : invStack ); + + return newUpgrade; + } + } + } + + return null; + } +} diff --git a/remappedSrc/dan200/computercraft/shared/pocket/core/PocketServerComputer.java b/remappedSrc/dan200/computercraft/shared/pocket/core/PocketServerComputer.java new file mode 100644 index 000000000..64a9a9698 --- /dev/null +++ b/remappedSrc/dan200/computercraft/shared/pocket/core/PocketServerComputer.java @@ -0,0 +1,198 @@ +/* + * 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.shared.pocket.core; + +import dan200.computercraft.ComputerCraft; +import dan200.computercraft.api.peripheral.IPeripheral; +import dan200.computercraft.api.pocket.IPocketAccess; +import dan200.computercraft.api.pocket.IPocketUpgrade; +import dan200.computercraft.core.computer.ComputerSide; +import dan200.computercraft.shared.common.IColouredItem; +import dan200.computercraft.shared.computer.core.ComputerFamily; +import dan200.computercraft.shared.computer.core.ServerComputer; +import dan200.computercraft.shared.network.NetworkHandler; +import dan200.computercraft.shared.pocket.items.ItemPocketComputer; +import dan200.computercraft.shared.util.NBTUtil; +import net.minecraft.entity.Entity; +import net.minecraft.entity.LivingEntity; +import net.minecraft.entity.player.PlayerEntity; +import net.minecraft.entity.player.PlayerInventory; +import net.minecraft.item.ItemStack; +import net.minecraft.nbt.CompoundTag; +import net.minecraft.server.network.ServerPlayerEntity; +import net.minecraft.util.Identifier; +import net.minecraft.world.World; + +import javax.annotation.Nonnull; +import javax.annotation.Nullable; +import java.util.Collections; +import java.util.Map; + +import static dan200.computercraft.shared.pocket.items.ItemPocketComputer.NBT_LIGHT; + +public class PocketServerComputer extends ServerComputer implements IPocketAccess +{ + private IPocketUpgrade m_upgrade; + private Entity m_entity; + private ItemStack m_stack; + + public PocketServerComputer( World world, int computerID, String label, int instanceID, ComputerFamily family ) + { + super( world, computerID, label, instanceID, family, ComputerCraft.terminalWidth_pocketComputer, ComputerCraft.terminalHeight_pocketComputer ); + } + + @Nullable + @Override + public Entity getEntity() + { + Entity entity = m_entity; + if( entity == null || m_stack == null || !entity.isAlive() ) return null; + + if( entity instanceof PlayerEntity ) + { + PlayerInventory inventory = ((PlayerEntity) entity).inventory; + return inventory.main.contains( m_stack ) || inventory.offHand.contains( m_stack ) ? entity : null; + } + else if( entity instanceof LivingEntity ) + { + LivingEntity living = (LivingEntity) entity; + return living.getMainHandStack() == m_stack || living.getOffHandStack() == m_stack ? entity : null; + } + else + { + return null; + } + } + + @Override + public int getColour() + { + return IColouredItem.getColourBasic( m_stack ); + } + + @Override + public void setColour( int colour ) + { + IColouredItem.setColourBasic( m_stack, colour ); + updateUpgradeNBTData(); + } + + @Override + public int getLight() + { + CompoundTag tag = getUserData(); + return tag.contains( NBT_LIGHT, NBTUtil.TAG_ANY_NUMERIC ) ? tag.getInt( NBT_LIGHT ) : -1; + } + + @Override + public void setLight( int colour ) + { + CompoundTag tag = getUserData(); + if( colour >= 0 && colour <= 0xFFFFFF ) + { + if( !tag.contains( NBT_LIGHT, NBTUtil.TAG_ANY_NUMERIC ) || tag.getInt( NBT_LIGHT ) != colour ) + { + tag.putInt( NBT_LIGHT, colour ); + updateUserData(); + } + } + else if( tag.contains( NBT_LIGHT, NBTUtil.TAG_ANY_NUMERIC ) ) + { + tag.remove( NBT_LIGHT ); + updateUserData(); + } + } + + @Nonnull + @Override + public CompoundTag getUpgradeNBTData() + { + return ItemPocketComputer.getUpgradeInfo( m_stack ); + } + + @Override + public void updateUpgradeNBTData() + { + if( m_entity instanceof PlayerEntity ) ((PlayerEntity) m_entity).inventory.markDirty(); + } + + @Override + public void invalidatePeripheral() + { + IPeripheral peripheral = m_upgrade == null ? null : m_upgrade.createPeripheral( this ); + setPeripheral( ComputerSide.BACK, peripheral ); + } + + @Nonnull + @Override + public Map getUpgrades() + { + return m_upgrade == null ? Collections.emptyMap() : Collections.singletonMap( m_upgrade.getUpgradeID(), getPeripheral( ComputerSide.BACK ) ); + } + + public IPocketUpgrade getUpgrade() + { + return m_upgrade; + } + + /** + * Set the upgrade for this pocket computer, also updating the item stack. + * + * Note this method is not thread safe - it must be called from the server thread. + * + * @param upgrade The new upgrade to set it to, may be {@code null}. + */ + public void setUpgrade( IPocketUpgrade upgrade ) + { + if( m_upgrade == upgrade ) return; + + synchronized( this ) + { + ItemPocketComputer.setUpgrade( m_stack, upgrade ); + updateUpgradeNBTData(); + m_upgrade = upgrade; + invalidatePeripheral(); + } + } + + public synchronized void updateValues( Entity entity, @Nonnull ItemStack stack, IPocketUpgrade upgrade ) + { + if( entity != null ) + { + setWorld( entity.getEntityWorld() ); + setPosition( entity.getBlockPos() ); + } + + // If a new entity has picked it up then rebroadcast the terminal to them + if( entity != m_entity && entity instanceof ServerPlayerEntity ) markTerminalChanged(); + + m_entity = entity; + m_stack = stack; + + if( m_upgrade != upgrade ) + { + m_upgrade = upgrade; + invalidatePeripheral(); + } + } + + @Override + public void broadcastState( boolean force ) + { + super.broadcastState( force ); + + if( (hasTerminalChanged() || force) && m_entity instanceof ServerPlayerEntity ) + { + // Broadcast the state to the current entity if they're not already interacting with it. + ServerPlayerEntity player = (ServerPlayerEntity) m_entity; + if( player.networkHandler != null && !isInteracting( player ) ) + { + NetworkHandler.sendToPlayer( player, createTerminalPacket() ); + } + } + } +} diff --git a/remappedSrc/dan200/computercraft/shared/pocket/inventory/ContainerPocketComputer.java b/remappedSrc/dan200/computercraft/shared/pocket/inventory/ContainerPocketComputer.java new file mode 100644 index 000000000..538172e11 --- /dev/null +++ b/remappedSrc/dan200/computercraft/shared/pocket/inventory/ContainerPocketComputer.java @@ -0,0 +1,52 @@ +/* + * 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.shared.pocket.inventory; + +import dan200.computercraft.shared.common.ContainerHeldItem; +import dan200.computercraft.shared.computer.core.IComputer; +import dan200.computercraft.shared.computer.core.IContainerComputer; +import dan200.computercraft.shared.computer.core.InputState; +import dan200.computercraft.shared.pocket.items.ItemPocketComputer; +import net.minecraft.entity.player.PlayerEntity; +import net.minecraft.item.ItemStack; +import net.minecraft.util.Hand; + +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class ContainerPocketComputer extends ContainerHeldItem implements IContainerComputer +{ + private final InputState input = new InputState( this ); + + public ContainerPocketComputer( int id, PlayerEntity player, Hand hand ) + { + super( id, player, hand ); + } + + @Nullable + @Override + public IComputer getComputer() + { + ItemStack stack = getStack(); + return !stack.isEmpty() && stack.getItem() instanceof ItemPocketComputer + ? ItemPocketComputer.getServerComputer( stack ) : null; + } + + @Nonnull + @Override + public InputState getInput() + { + return input; + } + + @Override + public void close( PlayerEntity player ) + { + super.close( player ); + input.close(); + } +} diff --git a/remappedSrc/dan200/computercraft/shared/pocket/items/ItemPocketComputer.java b/remappedSrc/dan200/computercraft/shared/pocket/items/ItemPocketComputer.java new file mode 100644 index 000000000..57728acca --- /dev/null +++ b/remappedSrc/dan200/computercraft/shared/pocket/items/ItemPocketComputer.java @@ -0,0 +1,395 @@ +/* + * 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.shared.pocket.items; + +import com.google.common.base.Objects; +import dan200.computercraft.ComputerCraft; +import dan200.computercraft.api.ComputerCraftAPI; +import dan200.computercraft.api.filesystem.IMount; +import dan200.computercraft.api.media.IMedia; +import dan200.computercraft.api.pocket.IPocketUpgrade; +import dan200.computercraft.core.computer.ComputerSide; +import dan200.computercraft.shared.PocketUpgrades; +import dan200.computercraft.shared.common.IColouredItem; +import dan200.computercraft.shared.computer.core.ClientComputer; +import dan200.computercraft.shared.computer.core.ComputerFamily; +import dan200.computercraft.shared.computer.core.ComputerState; +import dan200.computercraft.shared.computer.core.ServerComputer; +import dan200.computercraft.shared.computer.items.IComputerItem; +import dan200.computercraft.shared.network.Containers; +import dan200.computercraft.shared.pocket.apis.PocketAPI; +import dan200.computercraft.shared.pocket.core.PocketServerComputer; +import net.fabricmc.api.EnvType; +import net.fabricmc.api.Environment; +import net.minecraft.client.item.ModelPredicateProvider; +import net.minecraft.client.item.TooltipContext; +import net.minecraft.entity.Entity; +import net.minecraft.entity.player.PlayerEntity; +import net.minecraft.inventory.Inventory; +import net.minecraft.item.Item; +import net.minecraft.item.ItemGroup; +import net.minecraft.item.ItemStack; +import net.minecraft.nbt.CompoundTag; +import net.minecraft.text.LiteralText; +import net.minecraft.text.Text; +import net.minecraft.text.TranslatableText; +import net.minecraft.util.*; +import net.minecraft.util.collection.DefaultedList; +import net.minecraft.world.World; + +import javax.annotation.Nonnull; +import javax.annotation.Nullable; +import java.util.List; + +public class ItemPocketComputer extends Item implements IComputerItem, IMedia, IColouredItem +{ + private static final String NBT_UPGRADE = "Upgrade"; + private static final String NBT_UPGRADE_INFO = "UpgradeInfo"; + public static final String NBT_LIGHT = "Light"; + + private static final String NBT_INSTANCE = "Instanceid"; + private static final String NBT_SESSION = "SessionId"; + + private final ComputerFamily family; + + public ItemPocketComputer( Settings settings, ComputerFamily family ) + { + super( settings ); + this.family = family; + addPropertyGetter( new Identifier( ComputerCraft.MOD_ID, "state" ), COMPUTER_STATE ); + addPropertyGetter( new Identifier( ComputerCraft.MOD_ID, "coloured" ), COMPUTER_COLOURED ); + } + + public ItemStack create( int id, String label, int colour, IPocketUpgrade upgrade ) + { + ItemStack result = new ItemStack( this ); + if( id >= 0 ) result.getOrCreateTag().putInt( NBT_ID, id ); + if( label != null ) result.setCustomName( new LiteralText( label ) ); + if( upgrade != null ) result.getOrCreateTag().putString( NBT_UPGRADE, upgrade.getUpgradeID().toString() ); + if( colour != -1 ) result.getOrCreateTag().putInt( NBT_COLOUR, colour ); + return result; + } + + @Override + public void appendStacks( ItemGroup group, DefaultedList stacks ) + { + if( !isIn( group ) ) return; + stacks.add( create( -1, null, -1, null ) ); + for( IPocketUpgrade upgrade : PocketUpgrades.getVanillaUpgrades() ) + { + stacks.add( create( -1, null, -1, upgrade ) ); + } + } + + @Override + public void inventoryTick( ItemStack stack, World world, Entity entity, int slotNum, boolean selected ) + { + if( !world.isClient ) + { + // Server side + Inventory inventory = entity instanceof PlayerEntity ? ((PlayerEntity) entity).inventory : null; + PocketServerComputer computer = createServerComputer( world, inventory, entity, stack ); + if( computer != null ) + { + IPocketUpgrade upgrade = getUpgrade( stack ); + + // Ping computer + computer.keepAlive(); + computer.setWorld( world ); + computer.updateValues( entity, stack, upgrade ); + + // Sync ID + int id = computer.getID(); + if( id != getComputerID( stack ) ) + { + setComputerID( stack, id ); + if( inventory != null ) inventory.markDirty(); + } + + // Sync label + String label = computer.getLabel(); + if( !Objects.equal( label, getLabel( stack ) ) ) + { + setLabel( stack, label ); + if( inventory != null ) inventory.markDirty(); + } + + // Update pocket upgrade + if( upgrade != null ) + { + upgrade.update( computer, computer.getPeripheral( ComputerSide.BACK ) ); + } + } + } + else + { + // Client side + createClientComputer( stack ); + } + } + + @Nonnull + @Override + public TypedActionResult use( World world, PlayerEntity player, @Nonnull Hand hand ) + { + ItemStack stack = player.getStackInHand( hand ); + if( !world.isClient ) + { + PocketServerComputer computer = createServerComputer( world, player.inventory, player, stack ); + + boolean stop = false; + if( computer != null ) + { + computer.turnOn(); + + IPocketUpgrade upgrade = getUpgrade( stack ); + if( upgrade != null ) + { + computer.updateValues( player, stack, upgrade ); + stop = upgrade.onRightClick( world, computer, computer.getPeripheral( ComputerSide.BACK ) ); + } + } + + if( !stop ) Containers.openPocketComputerGUI( player, hand ); + } + return new TypedActionResult<>( ActionResult.SUCCESS, stack ); + } + + @Nonnull + @Override + public Text getName( @Nonnull ItemStack stack ) + { + String baseString = getTranslationKey( stack ); + IPocketUpgrade upgrade = getUpgrade( stack ); + if( upgrade != null ) + { + return new TranslatableText( baseString + ".upgraded", + new TranslatableText( upgrade.getUnlocalisedAdjective() ) + ); + } + else + { + return super.getName( stack ); + } + } + + + @Override + public void appendTooltip( ItemStack stack, @Nullable World world, List list, TooltipContext flag ) + { + if( flag.isAdvanced() ) + { + int id = getComputerID( stack ); + if( id >= 0 ) + { + list.add( new TranslatableText( "gui.computercraft.tooltip.computer_id", id ) + .formatted( Formatting.GRAY ) ); + } + } + } + + private PocketServerComputer createServerComputer( final World world, Inventory inventory, Entity entity, @Nonnull ItemStack stack ) + { + if( world.isClient ) return null; + + PocketServerComputer computer; + int instanceID = getInstanceID( stack ); + int sessionID = getSessionID( stack ); + int correctSessionID = ComputerCraft.serverComputerRegistry.getSessionID(); + + if( instanceID >= 0 && sessionID == correctSessionID && + ComputerCraft.serverComputerRegistry.contains( instanceID ) ) + { + computer = (PocketServerComputer) ComputerCraft.serverComputerRegistry.get( instanceID ); + } + else + { + if( instanceID < 0 || sessionID != correctSessionID ) + { + instanceID = ComputerCraft.serverComputerRegistry.getUnusedInstanceID(); + setInstanceID( stack, instanceID ); + setSessionID( stack, correctSessionID ); + } + int computerID = getComputerID( stack ); + if( computerID < 0 ) + { + computerID = ComputerCraftAPI.createUniqueNumberedSaveDir( world, "computer" ); + setComputerID( stack, computerID ); + } + computer = new PocketServerComputer( + world, + computerID, + getLabel( stack ), + instanceID, + getFamily() + ); + computer.updateValues( entity, stack, getUpgrade( stack ) ); + computer.addAPI( new PocketAPI( computer ) ); + ComputerCraft.serverComputerRegistry.add( instanceID, computer ); + if( inventory != null ) inventory.markDirty(); + } + computer.setWorld( world ); + return computer; + } + + public static ServerComputer getServerComputer( @Nonnull ItemStack stack ) + { + int instanceID = getInstanceID( stack ); + return instanceID >= 0 ? ComputerCraft.serverComputerRegistry.get( instanceID ) : null; + } + + public static ClientComputer createClientComputer( @Nonnull ItemStack stack ) + { + int instanceID = getInstanceID( stack ); + if( instanceID >= 0 ) + { + if( !ComputerCraft.clientComputerRegistry.contains( instanceID ) ) + { + ComputerCraft.clientComputerRegistry.add( instanceID, new ClientComputer( instanceID ) ); + } + return ComputerCraft.clientComputerRegistry.get( instanceID ); + } + return null; + } + + private static ClientComputer getClientComputer( @Nonnull ItemStack stack ) + { + int instanceID = getInstanceID( stack ); + return instanceID >= 0 ? ComputerCraft.clientComputerRegistry.get( instanceID ) : null; + } + + // IComputerItem implementation + + private static void setComputerID( @Nonnull ItemStack stack, int computerID ) + { + stack.getOrCreateTag().putInt( NBT_ID, computerID ); + } + + @Override + public String getLabel( @Nonnull ItemStack stack ) + { + return IComputerItem.super.getLabel( stack ); + } + + @Override + public ComputerFamily getFamily() + { + return family; + } + + @Override + public ItemStack withFamily( @Nonnull ItemStack stack, @Nonnull ComputerFamily family ) + { + return PocketComputerItemFactory.create( + getComputerID( stack ), getLabel( stack ), getColour( stack ), + family, getUpgrade( stack ) + ); + } + + // IMedia + + @Override + public boolean setLabel( @Nonnull ItemStack stack, String label ) + { + if( label != null ) + { + stack.setCustomName( new LiteralText( label ) ); + } + else + { + stack.removeCustomName(); + } + return true; + } + + @Override + public IMount createDataMount( @Nonnull ItemStack stack, @Nonnull World world ) + { + int id = getComputerID( stack ); + if( id >= 0 ) + { + return ComputerCraftAPI.createSaveDirMount( world, "computer/" + id, ComputerCraft.computerSpaceLimit ); + } + return null; + } + + private static int getInstanceID( @Nonnull ItemStack stack ) + { + CompoundTag nbt = stack.getTag(); + return nbt != null && nbt.contains( NBT_INSTANCE ) ? nbt.getInt( NBT_INSTANCE ) : -1; + } + + private static void setInstanceID( @Nonnull ItemStack stack, int instanceID ) + { + stack.getOrCreateTag().putInt( NBT_INSTANCE, instanceID ); + } + + private static int getSessionID( @Nonnull ItemStack stack ) + { + CompoundTag nbt = stack.getTag(); + return nbt != null && nbt.contains( NBT_SESSION ) ? nbt.getInt( NBT_SESSION ) : -1; + } + + private static void setSessionID( @Nonnull ItemStack stack, int sessionID ) + { + stack.getOrCreateTag().putInt( NBT_SESSION, sessionID ); + } + + @Environment( EnvType.CLIENT ) + public static ComputerState getState( @Nonnull ItemStack stack ) + { + ClientComputer computer = getClientComputer( stack ); + return computer == null ? ComputerState.OFF : computer.getState(); + } + + @Environment( EnvType.CLIENT ) + public static int getLightState( @Nonnull ItemStack stack ) + { + ClientComputer computer = getClientComputer( stack ); + if( computer != null && computer.isOn() ) + { + CompoundTag computerNBT = computer.getUserData(); + if( computerNBT != null && computerNBT.contains( NBT_LIGHT ) ) + { + return computerNBT.getInt( NBT_LIGHT ); + } + } + return -1; + } + + public static IPocketUpgrade getUpgrade( @Nonnull ItemStack stack ) + { + CompoundTag compound = stack.getTag(); + return compound != null && compound.contains( NBT_UPGRADE ) + ? PocketUpgrades.get( compound.getString( NBT_UPGRADE ) ) : null; + + } + + public static void setUpgrade( @Nonnull ItemStack stack, IPocketUpgrade upgrade ) + { + CompoundTag compound = stack.getOrCreateTag(); + + if( upgrade == null ) + { + compound.remove( NBT_UPGRADE ); + } + else + { + compound.putString( NBT_UPGRADE, upgrade.getUpgradeID().toString() ); + } + + compound.remove( NBT_UPGRADE_INFO ); + } + + public static CompoundTag getUpgradeInfo( @Nonnull ItemStack stack ) + { + return stack.getOrCreateSubTag( NBT_UPGRADE_INFO ); + } + + private static final ModelPredicateProvider COMPUTER_STATE = ( stack, world, player ) -> getState( stack ).ordinal(); + private static final ModelPredicateProvider COMPUTER_COLOURED = ( stack, world, player ) -> IColouredItem.getColourBasic( stack ) != -1 ? 1 : 0; +} diff --git a/remappedSrc/dan200/computercraft/shared/pocket/items/PocketComputerItemFactory.java b/remappedSrc/dan200/computercraft/shared/pocket/items/PocketComputerItemFactory.java new file mode 100644 index 000000000..6b64cf578 --- /dev/null +++ b/remappedSrc/dan200/computercraft/shared/pocket/items/PocketComputerItemFactory.java @@ -0,0 +1,33 @@ +/* + * 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.shared.pocket.items; + +import dan200.computercraft.ComputerCraft; +import dan200.computercraft.api.pocket.IPocketUpgrade; +import dan200.computercraft.shared.computer.core.ComputerFamily; +import net.minecraft.item.ItemStack; + +import javax.annotation.Nonnull; + +public final class PocketComputerItemFactory +{ + private PocketComputerItemFactory() {} + + @Nonnull + public static ItemStack create( int id, String label, int colour, ComputerFamily family, IPocketUpgrade upgrade ) + { + switch( family ) + { + case Normal: + return ComputerCraft.Items.pocketComputerNormal.create( id, label, colour, upgrade ); + case Advanced: + return ComputerCraft.Items.pocketComputerAdvanced.create( id, label, colour, upgrade ); + default: + return ItemStack.EMPTY; + } + } +} diff --git a/remappedSrc/dan200/computercraft/shared/pocket/peripherals/PocketModem.java b/remappedSrc/dan200/computercraft/shared/pocket/peripherals/PocketModem.java new file mode 100644 index 000000000..4ddc42b7c --- /dev/null +++ b/remappedSrc/dan200/computercraft/shared/pocket/peripherals/PocketModem.java @@ -0,0 +1,56 @@ +/* + * 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.shared.pocket.peripherals; + +import dan200.computercraft.ComputerCraft; +import dan200.computercraft.api.peripheral.IPeripheral; +import dan200.computercraft.api.pocket.AbstractPocketUpgrade; +import dan200.computercraft.api.pocket.IPocketAccess; +import dan200.computercraft.shared.peripheral.modem.ModemState; +import net.minecraft.entity.Entity; +import net.minecraft.util.Identifier; + +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class PocketModem extends AbstractPocketUpgrade +{ + private final boolean advanced; + + public PocketModem( boolean advanced ) + { + super( + new Identifier( "computercraft", advanced ? "wireless_modem_advanced" : "wireless_modem_normal" ), + advanced + ? ComputerCraft.Blocks.wirelessModemAdvanced + : ComputerCraft.Blocks.wirelessModemNormal + ); + this.advanced = advanced; + } + + @Nullable + @Override + public IPeripheral createPeripheral( @Nonnull IPocketAccess access ) + { + return new PocketModemPeripheral( advanced ); + } + + @Override + public void update( @Nonnull IPocketAccess access, @Nullable IPeripheral peripheral ) + { + if( !(peripheral instanceof PocketModemPeripheral) ) return; + + Entity entity = access.getEntity(); + + PocketModemPeripheral modem = (PocketModemPeripheral) peripheral; + + if( entity != null ) modem.setLocation( entity.getEntityWorld(), entity.getCameraPosVec( 1 ) ); + + ModemState state = modem.getModemState(); + if( state.pollChanged() ) access.setLight( state.isOpen() ? 0xBA0000 : -1 ); + } +} diff --git a/remappedSrc/dan200/computercraft/shared/pocket/peripherals/PocketModemPeripheral.java b/remappedSrc/dan200/computercraft/shared/pocket/peripherals/PocketModemPeripheral.java new file mode 100644 index 000000000..f34f31bc8 --- /dev/null +++ b/remappedSrc/dan200/computercraft/shared/pocket/peripherals/PocketModemPeripheral.java @@ -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.shared.pocket.peripherals; + +import dan200.computercraft.api.peripheral.IPeripheral; +import dan200.computercraft.shared.peripheral.modem.ModemState; +import dan200.computercraft.shared.peripheral.modem.wireless.WirelessModemPeripheral; +import net.minecraft.util.math.Vec3d; +import net.minecraft.world.World; + +import javax.annotation.Nonnull; + +public class PocketModemPeripheral extends WirelessModemPeripheral +{ + private World world = null; + private Vec3d position = Vec3d.ZERO; + + public PocketModemPeripheral( boolean advanced ) + { + super( new ModemState(), advanced ); + } + + void setLocation( World world, Vec3d position ) + { + this.position = position; + this.world = world; + } + + @Nonnull + @Override + public World getWorld() + { + return world; + } + + @Nonnull + @Override + public Vec3d getPosition() + { + return position; + } + + @Override + public boolean equals( IPeripheral other ) + { + return other instanceof PocketModemPeripheral; + } +} diff --git a/remappedSrc/dan200/computercraft/shared/pocket/peripherals/PocketSpeaker.java b/remappedSrc/dan200/computercraft/shared/pocket/peripherals/PocketSpeaker.java new file mode 100644 index 000000000..c7deb0856 --- /dev/null +++ b/remappedSrc/dan200/computercraft/shared/pocket/peripherals/PocketSpeaker.java @@ -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.shared.pocket.peripherals; + +import dan200.computercraft.ComputerCraft; +import dan200.computercraft.api.peripheral.IPeripheral; +import dan200.computercraft.api.pocket.AbstractPocketUpgrade; +import dan200.computercraft.api.pocket.IPocketAccess; +import net.minecraft.entity.Entity; +import net.minecraft.util.Identifier; + +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class PocketSpeaker extends AbstractPocketUpgrade +{ + public PocketSpeaker() + { + super( new Identifier( "computercraft", "speaker" ), ComputerCraft.Blocks.speaker ); + } + + @Nullable + @Override + public IPeripheral createPeripheral( @Nonnull IPocketAccess access ) + { + return new PocketSpeakerPeripheral(); + } + + @Override + public void update( @Nonnull IPocketAccess access, @Nullable IPeripheral peripheral ) + { + if( !(peripheral instanceof PocketSpeakerPeripheral) ) return; + + PocketSpeakerPeripheral speaker = (PocketSpeakerPeripheral) peripheral; + + Entity entity = access.getEntity(); + if( entity != null ) + { + speaker.setLocation( entity.getEntityWorld(), entity.getCameraPosVec( 1 ) ); + } + + speaker.update(); + access.setLight( speaker.madeSound( 20 ) ? 0x3320fc : -1 ); + } +} diff --git a/remappedSrc/dan200/computercraft/shared/pocket/peripherals/PocketSpeakerPeripheral.java b/remappedSrc/dan200/computercraft/shared/pocket/peripherals/PocketSpeakerPeripheral.java new file mode 100644 index 000000000..7202cd862 --- /dev/null +++ b/remappedSrc/dan200/computercraft/shared/pocket/peripherals/PocketSpeakerPeripheral.java @@ -0,0 +1,42 @@ +/* + * 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.shared.pocket.peripherals; + +import dan200.computercraft.api.peripheral.IPeripheral; +import dan200.computercraft.shared.peripheral.speaker.SpeakerPeripheral; +import net.minecraft.util.math.Vec3d; +import net.minecraft.world.World; + +public class PocketSpeakerPeripheral extends SpeakerPeripheral +{ + private World world = null; + private Vec3d position = Vec3d.ZERO; + + void setLocation( World world, Vec3d position ) + { + this.position = position; + this.world = world; + } + + @Override + public World getWorld() + { + return world; + } + + @Override + public Vec3d getPosition() + { + return world != null ? position : null; + } + + @Override + public boolean equals( IPeripheral other ) + { + return other instanceof PocketSpeakerPeripheral; + } +} diff --git a/remappedSrc/dan200/computercraft/shared/pocket/recipes/PocketComputerUpgradeRecipe.java b/remappedSrc/dan200/computercraft/shared/pocket/recipes/PocketComputerUpgradeRecipe.java new file mode 100644 index 000000000..06e799c0d --- /dev/null +++ b/remappedSrc/dan200/computercraft/shared/pocket/recipes/PocketComputerUpgradeRecipe.java @@ -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.shared.pocket.recipes; + +import dan200.computercraft.api.pocket.IPocketUpgrade; +import dan200.computercraft.shared.PocketUpgrades; +import dan200.computercraft.shared.computer.core.ComputerFamily; +import dan200.computercraft.shared.pocket.items.ItemPocketComputer; +import dan200.computercraft.shared.pocket.items.PocketComputerItemFactory; +import net.minecraft.inventory.CraftingInventory; +import net.minecraft.item.ItemStack; +import net.minecraft.recipe.RecipeSerializer; +import net.minecraft.recipe.SpecialCraftingRecipe; +import net.minecraft.recipe.SpecialRecipeSerializer; +import net.minecraft.util.Identifier; +import net.minecraft.world.World; + +import javax.annotation.Nonnull; + +public final class PocketComputerUpgradeRecipe extends SpecialCraftingRecipe +{ + private PocketComputerUpgradeRecipe( Identifier identifier ) + { + super( identifier ); + } + + @Override + public boolean fits( int x, int y ) + { + return x >= 2 && y >= 2; + } + + @Nonnull + @Override + public ItemStack getOutput() + { + return PocketComputerItemFactory.create( -1, null, -1, ComputerFamily.Normal, null ); + } + + @Override + public boolean matches( @Nonnull CraftingInventory inventory, @Nonnull World world ) + { + return !craft( inventory ).isEmpty(); + } + + @Nonnull + @Override + public ItemStack craft( @Nonnull CraftingInventory inventory ) + { + // Scan the grid for a pocket computer + ItemStack computer = ItemStack.EMPTY; + int computerX = -1; + int computerY = -1; + computer: + for( int y = 0; y < inventory.getHeight(); y++ ) + { + for( int x = 0; x < inventory.getWidth(); x++ ) + { + ItemStack item = inventory.getStack( x + y * inventory.getWidth() ); + if( !item.isEmpty() && item.getItem() instanceof ItemPocketComputer ) + { + computer = item; + computerX = x; + computerY = y; + break computer; + } + } + } + + if( computer.isEmpty() ) return ItemStack.EMPTY; + + ItemPocketComputer itemComputer = (ItemPocketComputer) computer.getItem(); + if( ItemPocketComputer.getUpgrade( computer ) != null ) return ItemStack.EMPTY; + + // Check for upgrades around the item + IPocketUpgrade upgrade = null; + for( int y = 0; y < inventory.getHeight(); y++ ) + { + for( int x = 0; x < inventory.getWidth(); x++ ) + { + ItemStack item = inventory.getStack( x + y * inventory.getWidth() ); + if( x == computerX && y == computerY ) continue; + + if( x == computerX && y == computerY - 1 ) + { + upgrade = PocketUpgrades.get( item ); + if( upgrade == null ) return ItemStack.EMPTY; + } + else if( !item.isEmpty() ) + { + return ItemStack.EMPTY; + } + } + } + + if( upgrade == null ) return ItemStack.EMPTY; + + // Construct the new stack + ComputerFamily family = itemComputer.getFamily(); + int computerID = itemComputer.getComputerID( computer ); + String label = itemComputer.getLabel( computer ); + int colour = itemComputer.getColour( computer ); + return PocketComputerItemFactory.create( computerID, label, colour, family, upgrade ); + } + + @Override + public RecipeSerializer getSerializer() + { + return SERIALIZER; + } + + public static final RecipeSerializer SERIALIZER = new SpecialRecipeSerializer<>( PocketComputerUpgradeRecipe::new ); +} diff --git a/remappedSrc/dan200/computercraft/shared/proxy/ComputerCraftProxyCommon.java b/remappedSrc/dan200/computercraft/shared/proxy/ComputerCraftProxyCommon.java new file mode 100644 index 000000000..c75a292d7 --- /dev/null +++ b/remappedSrc/dan200/computercraft/shared/proxy/ComputerCraftProxyCommon.java @@ -0,0 +1,167 @@ +/* + * 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.shared.proxy; + +import dan200.computercraft.ComputerCraft; +import dan200.computercraft.api.ComputerCraftAPI; +import dan200.computercraft.api.media.IMedia; +import dan200.computercraft.api.peripheral.IPeripheralTile; +import dan200.computercraft.api.turtle.event.TurtleEvent; +import dan200.computercraft.core.computer.MainThread; +import dan200.computercraft.core.tracking.Tracking; +import dan200.computercraft.shared.Registry; +import dan200.computercraft.shared.TurtlePermissions; +import dan200.computercraft.shared.command.CommandComputerCraft; +import dan200.computercraft.shared.command.arguments.ArgumentSerializers; +import dan200.computercraft.shared.common.ColourableRecipe; +import dan200.computercraft.shared.common.DefaultBundledRedstoneProvider; +import dan200.computercraft.shared.computer.recipe.ComputerUpgradeRecipe; +import dan200.computercraft.shared.media.items.RecordMedia; +import dan200.computercraft.shared.media.recipes.DiskRecipe; +import dan200.computercraft.shared.media.recipes.PrintoutRecipe; +import dan200.computercraft.shared.network.Containers; +import dan200.computercraft.shared.network.NetworkHandler; +import dan200.computercraft.shared.peripheral.commandblock.CommandBlockPeripheral; +import dan200.computercraft.shared.peripheral.modem.wireless.WirelessNetwork; +import dan200.computercraft.shared.pocket.recipes.PocketComputerUpgradeRecipe; +import dan200.computercraft.shared.turtle.FurnaceRefuelHandler; +import dan200.computercraft.shared.turtle.recipes.TurtleRecipe; +import dan200.computercraft.shared.turtle.recipes.TurtleUpgradeRecipe; +import dan200.computercraft.shared.util.ImpostorRecipe; +import dan200.computercraft.shared.util.ImpostorShapelessRecipe; +import dan200.computercraft.shared.util.TickScheduler; +import net.fabricmc.fabric.api.event.server.ServerStartCallback; +import net.fabricmc.fabric.api.event.server.ServerStopCallback; +import net.fabricmc.fabric.api.event.server.ServerTickCallback; +import net.fabricmc.fabric.api.registry.CommandRegistry; +import net.minecraft.block.entity.BlockEntity; +import net.minecraft.block.entity.BlockEntityType; +import net.minecraft.block.entity.CommandBlockBlockEntity; +import net.minecraft.item.Item; +import net.minecraft.item.MusicDiscItem; +import net.minecraft.recipe.RecipeSerializer; +import net.minecraft.server.MinecraftServer; +import net.minecraft.util.registry.MutableRegistry; + +public class ComputerCraftProxyCommon +{ + private static MinecraftServer server; + + public static void setup() + { + NetworkHandler.setup(); + + Registry.registerBlocks( net.minecraft.util.registry.Registry.BLOCK ); + Registry.registerTileEntities( (MutableRegistry>) net.minecraft.util.registry.Registry.BLOCK_ENTITY_TYPE ); + Registry.registerItems( net.minecraft.util.registry.Registry.ITEM ); + Registry.registerRecipes( (MutableRegistry>) net.minecraft.util.registry.Registry.RECIPE_SERIALIZER ); + + Containers.setup(); + + registerProviders(); + registerHandlers(); + + ArgumentSerializers.register(); + } + + private static void registerProviders() + { + // Register peripheral providers + ComputerCraftAPI.registerPeripheralProvider( ( world, pos, side ) -> { + BlockEntity tile = world.getBlockEntity( pos ); + return tile instanceof IPeripheralTile ? ((IPeripheralTile) tile).getPeripheral( side ) : null; + } ); + + ComputerCraftAPI.registerPeripheralProvider( ( world, pos, side ) -> { + BlockEntity tile = world.getBlockEntity( pos ); + return ComputerCraft.enableCommandBlock && tile instanceof CommandBlockBlockEntity ? new CommandBlockPeripheral( (CommandBlockBlockEntity) tile ) : null; + } ); + + // Register bundled power providers + ComputerCraftAPI.registerBundledRedstoneProvider( new DefaultBundledRedstoneProvider() ); + + // Register media providers + ComputerCraftAPI.registerMediaProvider( stack -> { + Item item = stack.getItem(); + if( item instanceof IMedia ) return (IMedia) item; + if( item instanceof MusicDiscItem ) return RecordMedia.INSTANCE; + return null; + } ); + } + + private static void registerHandlers() + { + CommandRegistry.INSTANCE.register( false, CommandComputerCraft::register ); + + ServerTickCallback.EVENT.register( server -> { + MainThread.executePendingTasks(); + ComputerCraft.serverComputerRegistry.update(); + TickScheduler.tick(); + } ); + + ServerStartCallback.EVENT.register( server -> { + ComputerCraftProxyCommon.server = server; + ComputerCraft.serverComputerRegistry.reset(); + WirelessNetwork.resetNetworks(); + MainThread.reset(); + Tracking.reset(); + } ); + + ServerStopCallback.EVENT.register( server -> { + ComputerCraft.serverComputerRegistry.reset(); + WirelessNetwork.resetNetworks(); + MainThread.reset(); + Tracking.reset(); + ComputerCraftProxyCommon.server = null; + } ); + + TurtleEvent.EVENT_BUS.register( FurnaceRefuelHandler.INSTANCE ); + TurtleEvent.EVENT_BUS.register( new TurtlePermissions() ); + } + + public static MinecraftServer getServer() + { + // Sorry asie + return server; + } + + public static final class ForgeHandlers + { + private ForgeHandlers() + { + } + + /* + @SubscribeEvent + public static void onConnectionOpened( FMLNetworkEvent.ClientConnectedToServerEvent event ) + { + ComputerCraft.clientComputerRegistry.reset(); + } + + @SubscribeEvent + public static void onConnectionClosed( FMLNetworkEvent.ClientDisconnectionFromServerEvent event ) + { + ComputerCraft.clientComputerRegistry.reset(); + } + + @SubscribeEvent + public static void onContainerOpen( PlayerContainerEvent.Open event ) + { + // If we're opening a computer container then broadcast the terminal state + Container container = event.getContainer(); + if( container instanceof IContainerComputer ) + { + IComputer computer = ((IContainerComputer) container).getComputer(); + if( computer instanceof ServerComputer ) + { + ((ServerComputer) computer).sendTerminalState( event.getEntityPlayer() ); + } + } + } + */ + } +} diff --git a/remappedSrc/dan200/computercraft/shared/turtle/FurnaceRefuelHandler.java b/remappedSrc/dan200/computercraft/shared/turtle/FurnaceRefuelHandler.java new file mode 100644 index 000000000..112f75535 --- /dev/null +++ b/remappedSrc/dan200/computercraft/shared/turtle/FurnaceRefuelHandler.java @@ -0,0 +1,62 @@ +/* + * 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.shared.turtle; + +import com.google.common.eventbus.Subscribe; +import dan200.computercraft.api.turtle.ITurtleAccess; +import dan200.computercraft.api.turtle.event.TurtleRefuelEvent; +import dan200.computercraft.shared.util.InventoryUtil; +import dan200.computercraft.shared.util.ItemStorage; +import dan200.computercraft.shared.util.WorldUtil; +import net.minecraft.block.entity.FurnaceBlockEntity; +import net.minecraft.item.Item; +import net.minecraft.item.ItemStack; + +import javax.annotation.Nonnull; + +public final class FurnaceRefuelHandler implements TurtleRefuelEvent.Handler +{ + public static final FurnaceRefuelHandler INSTANCE = new FurnaceRefuelHandler(); + + private FurnaceRefuelHandler() + { + } + + @Override + public int refuel( @Nonnull ITurtleAccess turtle, @Nonnull ItemStack currentStack, int slot, int limit ) + { + ItemStorage storage = ItemStorage.wrap( turtle.getInventory() ); + ItemStack stack = storage.take( slot, limit, ItemStack.EMPTY, false ); + int fuelToGive = getFuelPerItem( stack ) * stack.getCount(); + + // Store the replacement item in the inventory + Item replacementStack = stack.getItem().getRecipeRemainder(); + if( replacementStack != null ) + { + ItemStack remainder = InventoryUtil.storeItems( new ItemStack( replacementStack ), storage, turtle.getSelectedSlot() ); + if( !remainder.isEmpty() ) + { + WorldUtil.dropItemStack( remainder, turtle.getWorld(), turtle.getPosition(), turtle.getDirection().getOpposite() ); + } + } + + return fuelToGive; + } + + + private static int getFuelPerItem( @Nonnull ItemStack stack ) + { + int burnTime = FurnaceBlockEntity.createFuelTimeMap().getOrDefault( stack.getItem(), 0 ); + return (burnTime * 5) / 100; + } + + @Subscribe + public void onTurtleRefuel( TurtleRefuelEvent event ) + { + if( event.getHandler() == null && getFuelPerItem( event.getStack() ) > 0 ) event.setHandler( INSTANCE ); + } +} diff --git a/remappedSrc/dan200/computercraft/shared/turtle/apis/TurtleAPI.java b/remappedSrc/dan200/computercraft/shared/turtle/apis/TurtleAPI.java new file mode 100644 index 000000000..ae0b1996a --- /dev/null +++ b/remappedSrc/dan200/computercraft/shared/turtle/apis/TurtleAPI.java @@ -0,0 +1,365 @@ +/* + * 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.shared.turtle.apis; + +import dan200.computercraft.api.lua.ILuaAPI; +import dan200.computercraft.api.lua.ILuaContext; +import dan200.computercraft.api.lua.LuaException; +import dan200.computercraft.api.turtle.ITurtleAccess; +import dan200.computercraft.api.turtle.ITurtleCommand; +import dan200.computercraft.api.turtle.TurtleCommandResult; +import dan200.computercraft.api.turtle.TurtleSide; +import dan200.computercraft.api.turtle.event.TurtleActionEvent; +import dan200.computercraft.api.turtle.event.TurtleEvent; +import dan200.computercraft.api.turtle.event.TurtleInspectItemEvent; +import dan200.computercraft.core.apis.IAPIEnvironment; +import dan200.computercraft.core.tracking.TrackingField; +import dan200.computercraft.shared.turtle.core.*; +import net.minecraft.item.Item; +import net.minecraft.item.ItemStack; +import net.minecraft.util.registry.Registry; + +import javax.annotation.Nonnull; +import javax.annotation.Nullable; +import java.util.HashMap; +import java.util.Map; + +import static dan200.computercraft.core.apis.ArgumentHelper.*; + +public class TurtleAPI implements ILuaAPI +{ + private IAPIEnvironment m_environment; + private ITurtleAccess m_turtle; + + public TurtleAPI( IAPIEnvironment environment, ITurtleAccess turtle ) + { + m_environment = environment; + m_turtle = turtle; + } + + // ILuaAPI implementation + + @Override + public String[] getNames() + { + return new String[] { + "turtle" + }; + } + + @Nonnull + @Override + public String[] getMethodNames() + { + return new String[] { + "forward", + "back", + "up", + "down", + "turnLeft", + "turnRight", + "dig", + "digUp", + "digDown", + "place", + "placeUp", + "placeDown", + "drop", + "select", + "getItemCount", + "getItemSpace", + "detect", + "detectUp", + "detectDown", + "compare", + "compareUp", + "compareDown", + "attack", + "attackUp", + "attackDown", + "dropUp", + "dropDown", + "suck", + "suckUp", + "suckDown", + "getFuelLevel", + "refuel", + "compareTo", + "transferTo", + "getSelectedSlot", + "getFuelLimit", + "equipLeft", + "equipRight", + "inspect", + "inspectUp", + "inspectDown", + "getItemDetail", + }; + } + + private Object[] tryCommand( ILuaContext context, ITurtleCommand command ) throws LuaException, InterruptedException + { + return m_turtle.executeCommand( context, command ); + } + + private int parseSlotNumber( Object[] arguments, int index ) throws LuaException + { + int slot = getInt( arguments, index ); + if( slot < 1 || slot > 16 ) throw new LuaException( "Slot number " + slot + " out of range" ); + return slot - 1; + } + + private int parseOptionalSlotNumber( Object[] arguments, int index, int fallback ) throws LuaException + { + if( index >= arguments.length || arguments[index] == null ) return fallback; + return parseSlotNumber( arguments, index ); + } + + private static int parseCount( Object[] arguments, int index ) throws LuaException + { + int count = optInt( arguments, index, 64 ); + if( count < 0 || count > 64 ) throw new LuaException( "Item count " + count + " out of range" ); + return count; + } + + @Nullable + private static TurtleSide parseSide( Object[] arguments, int index ) throws LuaException + { + String side = optString( arguments, index, null ); + if( side == null ) + { + return null; + } + else if( side.equalsIgnoreCase( "left" ) ) + { + return TurtleSide.Left; + } + else if( side.equalsIgnoreCase( "right" ) ) + { + return TurtleSide.Right; + } + else + { + throw new LuaException( "Invalid side" ); + } + } + + @Override + public Object[] callMethod( @Nonnull ILuaContext context, int method, @Nonnull Object[] args ) throws LuaException, InterruptedException + { + switch( method ) + { + case 0: // forward + m_environment.addTrackingChange( TrackingField.TURTLE_OPS ); + return tryCommand( context, new TurtleMoveCommand( MoveDirection.Forward ) ); + case 1: // back + m_environment.addTrackingChange( TrackingField.TURTLE_OPS ); + return tryCommand( context, new TurtleMoveCommand( MoveDirection.Back ) ); + case 2: // up + m_environment.addTrackingChange( TrackingField.TURTLE_OPS ); + return tryCommand( context, new TurtleMoveCommand( MoveDirection.Up ) ); + case 3: // down + m_environment.addTrackingChange( TrackingField.TURTLE_OPS ); + return tryCommand( context, new TurtleMoveCommand( MoveDirection.Down ) ); + case 4: // turnLeft + m_environment.addTrackingChange( TrackingField.TURTLE_OPS ); + return tryCommand( context, new TurtleTurnCommand( TurnDirection.Left ) ); + case 5: // turnRight + m_environment.addTrackingChange( TrackingField.TURTLE_OPS ); + return tryCommand( context, new TurtleTurnCommand( TurnDirection.Right ) ); + case 6: + { + // dig + TurtleSide side = parseSide( args, 0 ); + m_environment.addTrackingChange( TrackingField.TURTLE_OPS ); + return tryCommand( context, TurtleToolCommand.dig( InteractDirection.Forward, side ) ); + } + case 7: + { + // digUp + TurtleSide side = parseSide( args, 0 ); + m_environment.addTrackingChange( TrackingField.TURTLE_OPS ); + return tryCommand( context, TurtleToolCommand.dig( InteractDirection.Up, side ) ); + } + case 8: + { + // digDown + TurtleSide side = parseSide( args, 0 ); + m_environment.addTrackingChange( TrackingField.TURTLE_OPS ); + return tryCommand( context, TurtleToolCommand.dig( InteractDirection.Down, side ) ); + } + case 9: // place + m_environment.addTrackingChange( TrackingField.TURTLE_OPS ); + return tryCommand( context, new TurtlePlaceCommand( InteractDirection.Forward, args ) ); + case 10: // placeUp + m_environment.addTrackingChange( TrackingField.TURTLE_OPS ); + return tryCommand( context, new TurtlePlaceCommand( InteractDirection.Up, args ) ); + case 11: // placeDown + m_environment.addTrackingChange( TrackingField.TURTLE_OPS ); + return tryCommand( context, new TurtlePlaceCommand( InteractDirection.Down, args ) ); + case 12: + { + // drop + int count = parseCount( args, 0 ); + m_environment.addTrackingChange( TrackingField.TURTLE_OPS ); + return tryCommand( context, new TurtleDropCommand( InteractDirection.Forward, count ) ); + } + case 13: + { + // select + int slot = parseSlotNumber( args, 0 ); + return tryCommand( context, turtle -> { + turtle.setSelectedSlot( slot ); + return TurtleCommandResult.success(); + } ); + } + case 14: + { + // getItemCount + int slot = parseOptionalSlotNumber( args, 0, m_turtle.getSelectedSlot() ); + ItemStack stack = m_turtle.getInventory().getStack( slot ); + return new Object[] { stack.getCount() }; + } + case 15: + { + // getItemSpace + int slot = parseOptionalSlotNumber( args, 0, m_turtle.getSelectedSlot() ); + ItemStack stack = m_turtle.getInventory().getStack( slot ); + return new Object[] { stack.isEmpty() ? 64 : Math.min( stack.getMaxCount(), 64 ) - stack.getCount() }; + } + case 16: // detect + return tryCommand( context, new TurtleDetectCommand( InteractDirection.Forward ) ); + case 17: // detectUp + return tryCommand( context, new TurtleDetectCommand( InteractDirection.Up ) ); + case 18: // detectDown + return tryCommand( context, new TurtleDetectCommand( InteractDirection.Down ) ); + case 19: // compare + return tryCommand( context, new TurtleCompareCommand( InteractDirection.Forward ) ); + case 20: // compareUp + return tryCommand( context, new TurtleCompareCommand( InteractDirection.Up ) ); + case 21: // compareDown + return tryCommand( context, new TurtleCompareCommand( InteractDirection.Down ) ); + case 22: + { + // attack + TurtleSide side = parseSide( args, 0 ); + m_environment.addTrackingChange( TrackingField.TURTLE_OPS ); + return tryCommand( context, TurtleToolCommand.attack( InteractDirection.Forward, side ) ); + } + case 23: + { + // attackUp + TurtleSide side = parseSide( args, 0 ); + m_environment.addTrackingChange( TrackingField.TURTLE_OPS ); + return tryCommand( context, TurtleToolCommand.attack( InteractDirection.Up, side ) ); + } + case 24: + { + // attackDown + TurtleSide side = parseSide( args, 0 ); + m_environment.addTrackingChange( TrackingField.TURTLE_OPS ); + return tryCommand( context, TurtleToolCommand.attack( InteractDirection.Down, side ) ); + } + case 25: + { + // dropUp + int count = parseCount( args, 0 ); + m_environment.addTrackingChange( TrackingField.TURTLE_OPS ); + return tryCommand( context, new TurtleDropCommand( InteractDirection.Up, count ) ); + } + case 26: + { + // dropDown + int count = parseCount( args, 0 ); + m_environment.addTrackingChange( TrackingField.TURTLE_OPS ); + return tryCommand( context, new TurtleDropCommand( InteractDirection.Down, count ) ); + } + case 27: + { + // suck + int count = parseCount( args, 0 ); + m_environment.addTrackingChange( TrackingField.TURTLE_OPS ); + return tryCommand( context, new TurtleSuckCommand( InteractDirection.Forward, count ) ); + } + case 28: + { + // suckUp + int count = parseCount( args, 0 ); + m_environment.addTrackingChange( TrackingField.TURTLE_OPS ); + return tryCommand( context, new TurtleSuckCommand( InteractDirection.Up, count ) ); + } + case 29: + { + // suckDown + int count = parseCount( args, 0 ); + m_environment.addTrackingChange( TrackingField.TURTLE_OPS ); + return tryCommand( context, new TurtleSuckCommand( InteractDirection.Down, count ) ); + } + case 30: // getFuelLevel + return new Object[] { m_turtle.isFuelNeeded() ? m_turtle.getFuelLevel() : "unlimited" }; + case 31: + { + // refuel + int count = optInt( args, 0, Integer.MAX_VALUE ); + if( count < 0 ) throw new LuaException( "Refuel count " + count + " out of range" ); + return tryCommand( context, new TurtleRefuelCommand( count ) ); + } + case 32: + { + // compareTo + int slot = parseSlotNumber( args, 0 ); + return tryCommand( context, new TurtleCompareToCommand( slot ) ); + } + case 33: + { + // transferTo + int slot = parseSlotNumber( args, 0 ); + int count = parseCount( args, 1 ); + return tryCommand( context, new TurtleTransferToCommand( slot, count ) ); + } + case 34: // getSelectedSlot + return new Object[] { m_turtle.getSelectedSlot() + 1 }; + case 35: // getFuelLimit + return new Object[] { m_turtle.isFuelNeeded() ? m_turtle.getFuelLimit() : "unlimited" }; + case 36: // equipLeft + m_environment.addTrackingChange( TrackingField.TURTLE_OPS ); + return tryCommand( context, new TurtleEquipCommand( TurtleSide.Left ) ); + case 37: // equipRight + m_environment.addTrackingChange( TrackingField.TURTLE_OPS ); + return tryCommand( context, new TurtleEquipCommand( TurtleSide.Right ) ); + case 38: // inspect + return tryCommand( context, new TurtleInspectCommand( InteractDirection.Forward ) ); + case 39: // inspectUp + return tryCommand( context, new TurtleInspectCommand( InteractDirection.Up ) ); + case 40: // inspectDown + return tryCommand( context, new TurtleInspectCommand( InteractDirection.Down ) ); + case 41: + { + // getItemDetail + int slot = parseOptionalSlotNumber( args, 0, m_turtle.getSelectedSlot() ); + ItemStack stack = m_turtle.getInventory().getStack( slot ); + if( stack.isEmpty() ) return new Object[] { null }; + + Item item = stack.getItem(); + String name = Registry.ITEM.getId( item ).toString(); + int count = stack.getCount(); + + Map table = new HashMap<>(); + table.put( "name", name ); + table.put( "count", count ); + + TurtleActionEvent event = new TurtleInspectItemEvent( m_turtle, stack, table ); + if( TurtleEvent.post( event ) ) return new Object[] { false, event.getFailureMessage() }; + + return new Object[] { table }; + } + + default: + return null; + } + } +} diff --git a/remappedSrc/dan200/computercraft/shared/turtle/blocks/BlockTurtle.java b/remappedSrc/dan200/computercraft/shared/turtle/blocks/BlockTurtle.java new file mode 100644 index 000000000..2f5f32f51 --- /dev/null +++ b/remappedSrc/dan200/computercraft/shared/turtle/blocks/BlockTurtle.java @@ -0,0 +1,155 @@ +/* + * 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.shared.turtle.blocks; + +import dan200.computercraft.api.turtle.TurtleSide; +import dan200.computercraft.shared.computer.blocks.BlockComputerBase; +import dan200.computercraft.shared.computer.blocks.TileComputerBase; +import dan200.computercraft.shared.computer.core.ComputerFamily; +import dan200.computercraft.shared.turtle.core.TurtleBrain; +import dan200.computercraft.shared.turtle.items.ITurtleItem; +import dan200.computercraft.shared.turtle.items.TurtleItemFactory; +import dan200.computercraft.shared.util.NamedBlockEntityType; +import dan200.computercraft.shared.util.WaterloggableBlock; +import net.minecraft.block.Block; +import net.minecraft.block.BlockRenderType; +import net.minecraft.block.BlockState; +import net.minecraft.block.ShapeContext; +import net.minecraft.block.entity.BlockEntity; +import net.minecraft.entity.LivingEntity; +import net.minecraft.entity.player.PlayerEntity; +import net.minecraft.fluid.FluidState; +import net.minecraft.item.ItemPlacementContext; +import net.minecraft.item.ItemStack; +import net.minecraft.state.StateManager; +import net.minecraft.state.property.DirectionProperty; +import net.minecraft.state.property.Properties; +import net.minecraft.util.Identifier; +import net.minecraft.util.math.BlockPos; +import net.minecraft.util.math.Direction; +import net.minecraft.util.math.Vec3d; +import net.minecraft.util.shape.VoxelShape; +import net.minecraft.util.shape.VoxelShapes; +import net.minecraft.world.BlockView; +import net.minecraft.world.World; +import net.minecraft.world.WorldAccess; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class BlockTurtle extends BlockComputerBase implements WaterloggableBlock +{ + public static final DirectionProperty FACING = Properties.HORIZONTAL_FACING; + + private static final VoxelShape DEFAULT_SHAPE = VoxelShapes.cuboid( + 0.125, 0.125, 0.125, + 0.875, 0.875, 0.875 + ); + + public BlockTurtle( Settings settings, ComputerFamily family, NamedBlockEntityType type ) + { + super( settings, family, type ); + setDefaultState( getStateManager().getDefaultState() + .with( FACING, Direction.NORTH ) + .with( WATERLOGGED, false ) + ); + } + + @Override + protected void appendProperties( StateManager.Builder builder ) + { + builder.add( FACING, WATERLOGGED ); + } + + @Nonnull + @Override + @Deprecated + public BlockRenderType getRenderType( BlockState state ) + { + return BlockRenderType.INVISIBLE; + } + + @Nonnull + @Override + @Deprecated + public VoxelShape getOutlineShape( BlockState state, BlockView world, BlockPos pos, ShapeContext position ) + { + BlockEntity tile = world.getBlockEntity( pos ); + Vec3d offset = tile instanceof TileTurtle ? ((TileTurtle) tile).getRenderOffset( 1.0f ) : Vec3d.ZERO; + return offset.equals( Vec3d.ZERO ) ? DEFAULT_SHAPE : DEFAULT_SHAPE.offset( offset.x, offset.y, offset.z ); + } + + @Nullable + @Override + public BlockState getPlacementState( ItemPlacementContext placement ) + { + return getDefaultState() + .with( FACING, placement.getPlayerFacing() ) + .with( WATERLOGGED, getWaterloggedStateForPlacement( placement ) ); + } + + @Nonnull + @Override + @Deprecated + public FluidState getFluidState( BlockState state ) + { + return getWaterloggedFluidState( state ); + } + + @Nonnull + @Override + @Deprecated + public BlockState getStateForNeighborUpdate( @Nonnull BlockState state, Direction side, BlockState otherState, WorldAccess world, BlockPos pos, BlockPos otherPos ) + { + updateWaterloggedPostPlacement( state, world, pos ); + return state; + } + + @Override + public void onPlaced( World world, BlockPos pos, BlockState state, @Nullable LivingEntity player, @Nonnull ItemStack stack ) + { + super.onPlaced( world, pos, state, player, stack ); + + BlockEntity tile = world.getBlockEntity( pos ); + if( !world.isClient && tile instanceof TileTurtle ) + { + TileTurtle turtle = (TileTurtle) tile; + + if( player instanceof PlayerEntity ) + { + ((TileTurtle) tile).setOwningPlayer( ((PlayerEntity) player).getGameProfile() ); + } + + if( stack.getItem() instanceof ITurtleItem ) + { + ITurtleItem item = (ITurtleItem) stack.getItem(); + + // Set Upgrades + for( TurtleSide side : TurtleSide.values() ) + { + turtle.getAccess().setUpgrade( side, item.getUpgrade( stack, side ) ); + } + + turtle.getAccess().setFuelLevel( item.getFuelLevel( stack ) ); + + // Set colour + int colour = item.getColour( stack ); + if( colour != -1 ) turtle.getAccess().setColour( colour ); + + // Set overlay + Identifier overlay = item.getOverlay( stack ); + if( overlay != null ) ((TurtleBrain) turtle.getAccess()).setOverlay( overlay ); + } + } + } + + @Nonnull + @Override + protected ItemStack getItem( TileComputerBase tile ) + { + return tile instanceof TileTurtle ? TurtleItemFactory.create( (TileTurtle) tile ) : ItemStack.EMPTY; + } +} diff --git a/remappedSrc/dan200/computercraft/shared/turtle/blocks/ITurtleTile.java b/remappedSrc/dan200/computercraft/shared/turtle/blocks/ITurtleTile.java new file mode 100644 index 000000000..fd13056ea --- /dev/null +++ b/remappedSrc/dan200/computercraft/shared/turtle/blocks/ITurtleTile.java @@ -0,0 +1,31 @@ +/* + * 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.shared.turtle.blocks; + +import dan200.computercraft.api.turtle.ITurtleAccess; +import dan200.computercraft.api.turtle.ITurtleUpgrade; +import dan200.computercraft.api.turtle.TurtleSide; +import dan200.computercraft.shared.computer.blocks.IComputerTile; +import net.minecraft.util.Identifier; +import net.minecraft.util.math.Vec3d; + +public interface ITurtleTile extends IComputerTile +{ + int getColour(); + + Identifier getOverlay(); + + ITurtleUpgrade getUpgrade( TurtleSide side ); + + ITurtleAccess getAccess(); + + Vec3d getRenderOffset( float f ); + + float getRenderYaw( float f ); + + float getToolRenderAngle( TurtleSide side, float f ); +} diff --git a/remappedSrc/dan200/computercraft/shared/turtle/blocks/TileTurtle.java b/remappedSrc/dan200/computercraft/shared/turtle/blocks/TileTurtle.java new file mode 100644 index 000000000..196d79632 --- /dev/null +++ b/remappedSrc/dan200/computercraft/shared/turtle/blocks/TileTurtle.java @@ -0,0 +1,588 @@ +/* + * 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.shared.turtle.blocks; + +import com.mojang.authlib.GameProfile; +import dan200.computercraft.ComputerCraft; +import dan200.computercraft.api.peripheral.IPeripheral; +import dan200.computercraft.api.turtle.ITurtleAccess; +import dan200.computercraft.api.turtle.ITurtleUpgrade; +import dan200.computercraft.api.turtle.TurtleSide; +import dan200.computercraft.core.computer.ComputerSide; +import dan200.computercraft.shared.common.TileGeneric; +import dan200.computercraft.shared.computer.blocks.ComputerPeripheral; +import dan200.computercraft.shared.computer.blocks.ComputerProxy; +import dan200.computercraft.shared.computer.blocks.TileComputerBase; +import dan200.computercraft.shared.computer.core.ComputerFamily; +import dan200.computercraft.shared.computer.core.ComputerState; +import dan200.computercraft.shared.computer.core.ServerComputer; +import dan200.computercraft.shared.network.Containers; +import dan200.computercraft.shared.turtle.apis.TurtleAPI; +import dan200.computercraft.shared.turtle.blocks.TileTurtle.MoveState; +import dan200.computercraft.shared.turtle.core.TurtleBrain; +import dan200.computercraft.shared.util.*; +import net.minecraft.block.entity.BlockEntityType; +import net.minecraft.entity.player.PlayerEntity; +import net.minecraft.item.DyeItem; +import net.minecraft.item.ItemStack; +import net.minecraft.item.Items; +import net.minecraft.nbt.CompoundTag; +import net.minecraft.nbt.ListTag; +import net.minecraft.util.*; +import net.minecraft.util.collection.DefaultedList; +import net.minecraft.util.hit.BlockHitResult; +import net.minecraft.util.math.BlockPos; +import net.minecraft.util.math.Direction; +import net.minecraft.util.math.Vec3d; + +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class TileTurtle extends TileComputerBase implements ITurtleTile, DefaultInventory, Nameable +{ + // Statics + + public static final int INVENTORY_SIZE = 16; + public static final int INVENTORY_WIDTH = 4; + public static final int INVENTORY_HEIGHT = 4; + + public static final NamedBlockEntityType FACTORY_NORMAL = NamedBlockEntityType.create( + new Identifier( ComputerCraft.MOD_ID, "turtle_normal" ), + type -> new TileTurtle( type, ComputerFamily.Normal ) + ); + + public static final NamedBlockEntityType FACTORY_ADVANCED = NamedBlockEntityType.create( + new Identifier( ComputerCraft.MOD_ID, "turtle_advanced" ), + type -> new TileTurtle( type, ComputerFamily.Advanced ) + ); + + // Members + + enum MoveState + { + NOT_MOVED, + IN_PROGRESS, + MOVED + } + + private DefaultedList m_inventory; + private DefaultedList m_previousInventory; + private boolean m_inventoryChanged; + private TurtleBrain m_brain; + private MoveState m_moveState; + + public TileTurtle( BlockEntityType type, ComputerFamily family ) + { + super( type, family ); + m_inventory = DefaultedList.ofSize( INVENTORY_SIZE, ItemStack.EMPTY ); + m_previousInventory = DefaultedList.ofSize( INVENTORY_SIZE, ItemStack.EMPTY ); + m_inventoryChanged = false; + m_brain = new TurtleBrain( this ); + m_moveState = MoveState.NOT_MOVED; + } + + public boolean hasMoved() + { + return m_moveState == MoveState.MOVED; + } + + @Override + protected ServerComputer createComputer( int instanceID, int id ) + { + ServerComputer computer = new ServerComputer( + getWorld(), id, m_label, instanceID, getFamily(), + ComputerCraft.terminalWidth_turtle, ComputerCraft.terminalHeight_turtle + ); + computer.setPosition( getPos() ); + computer.addAPI( new TurtleAPI( computer.getAPIEnvironment(), getAccess() ) ); + m_brain.setupComputer( computer ); + return computer; + } + + @Override + public ComputerProxy createProxy() + { + return m_brain.getProxy(); + } + + @Override + public void destroy() + { + if( !hasMoved() ) + { + // Stop computer + super.destroy(); + + // Drop contents + if( !getWorld().isClient ) + { + int size = size(); + for( int i = 0; i < size; i++ ) + { + ItemStack stack = getStack( i ); + if( !stack.isEmpty() ) + { + WorldUtil.dropItemStack( stack, getWorld(), getPos() ); + } + } + } + } + else + { + // Just turn off any redstone we had on + for( Direction dir : DirectionUtil.FACINGS ) + { + RedstoneUtil.propagateRedstoneOutput( getWorld(), getPos(), dir ); + } + } + } + + @Override + protected void unload() + { + if( !hasMoved() ) + { + super.unload(); + } + } + + @Override + public boolean onActivate( PlayerEntity player, Hand hand, BlockHitResult hit ) + { + // Apply dye + ItemStack currentItem = player.getStackInHand( hand ); + if( !currentItem.isEmpty() ) + { + if( currentItem.getItem() instanceof DyeItem ) + { + // Dye to change turtle colour + if( !getWorld().isClient ) + { + DyeColor dye = ((DyeItem) currentItem.getItem()).getColor(); + if( m_brain.getDyeColour() != dye ) + { + m_brain.setDyeColour( dye ); + if( !player.isCreative() ) + { + currentItem.decrement( 1 ); + } + } + } + return true; + } + else if( currentItem.getItem() == Items.WATER_BUCKET && m_brain.getColour() != -1 ) + { + // Water to remove turtle colour + if( !getWorld().isClient ) + { + if( m_brain.getColour() != -1 ) + { + m_brain.setColour( -1 ); + if( !player.isCreative() ) + { + player.setStackInHand( hand, new ItemStack( Items.BUCKET ) ); + player.inventory.markDirty(); + } + } + } + return true; + } + } + + // Open GUI or whatever + return super.onActivate( player, hand, hit ); + } + + @Override + protected boolean canNameWithTag( PlayerEntity player ) + { + return true; + } + + @Override + public void openGUI( PlayerEntity player ) + { + Containers.openTurtleGUI( player, this ); + } + + @Override + protected double getInteractRange( PlayerEntity player ) + { + return 12.0; + } + + @Override + public void tick() + { + super.tick(); + m_brain.update(); + synchronized( m_inventory ) + { + if( !getWorld().isClient && m_inventoryChanged ) + { + ServerComputer computer = getServerComputer(); + if( computer != null ) computer.queueEvent( "turtle_inventory" ); + + m_inventoryChanged = false; + for( int n = 0; n < size(); n++ ) + { + m_previousInventory.set( n, InventoryUtil.copyItem( getStack( n ) ) ); + } + } + } + } + + @Override + protected void updateBlockState( ComputerState newState ) + { + } + + @Override + public void onNeighbourChange( @Nonnull BlockPos neighbour ) + { + if( m_moveState == MoveState.NOT_MOVED ) super.onNeighbourChange( neighbour ); + } + + @Override + public void onNeighbourTileEntityChange( @Nonnull BlockPos neighbour ) + { + if( m_moveState == MoveState.NOT_MOVED ) super.onNeighbourTileEntityChange( neighbour ); + } + + public void notifyMoveStart() + { + if( m_moveState == MoveState.NOT_MOVED ) m_moveState = MoveState.IN_PROGRESS; + } + + public void notifyMoveEnd() + { + // MoveState.MOVED is final + if( m_moveState == MoveState.IN_PROGRESS ) m_moveState = MoveState.NOT_MOVED; + } + + @Override + public void fromTag( CompoundTag nbt ) + { + super.fromTag( nbt ); + + // Read inventory + ListTag nbttaglist = nbt.getList( "Items", NBTUtil.TAG_COMPOUND ); + m_inventory = DefaultedList.ofSize( INVENTORY_SIZE, ItemStack.EMPTY ); + m_previousInventory = DefaultedList.ofSize( INVENTORY_SIZE, ItemStack.EMPTY ); + for( int i = 0; i < nbttaglist.size(); i++ ) + { + CompoundTag tag = nbttaglist.getCompound( i ); + int slot = tag.getByte( "Slot" ) & 0xff; + if( slot < size() ) + { + m_inventory.set( slot, ItemStack.fromTag( tag ) ); + m_previousInventory.set( slot, InventoryUtil.copyItem( m_inventory.get( slot ) ) ); + } + } + + // Read state + m_brain.readFromNBT( nbt ); + } + + @Nonnull + @Override + public CompoundTag toTag( CompoundTag nbt ) + { + // Write inventory + ListTag nbttaglist = new ListTag(); + for( int i = 0; i < INVENTORY_SIZE; i++ ) + { + if( !m_inventory.get( i ).isEmpty() ) + { + CompoundTag tag = new CompoundTag(); + tag.putByte( "Slot", (byte) i ); + m_inventory.get( i ).toTag( tag ); + nbttaglist.add( tag ); + } + } + nbt.put( "Items", nbttaglist ); + + // Write brain + nbt = m_brain.writeToNBT( nbt ); + + return super.toTag( nbt ); + } + + @Override + protected boolean isPeripheralBlockedOnSide( ComputerSide localSide ) + { + return hasPeripheralUpgradeOnSide( localSide ); + } + + // IDirectionalTile + + @Override + public Direction getDirection() + { + return getCachedState().get( BlockTurtle.FACING ); + } + + public void setDirection( Direction dir ) + { + if( dir.getAxis() == Direction.Axis.Y ) dir = Direction.NORTH; + world.setBlockState( pos, getCachedState().with( BlockTurtle.FACING, dir ) ); + updateOutput(); + updateInput(); + onTileEntityChange(); + } + + // ITurtleTile + + @Override + public ITurtleUpgrade getUpgrade( TurtleSide side ) + { + return m_brain.getUpgrade( side ); + } + + @Override + public int getColour() + { + return m_brain.getColour(); + } + + @Override + public Identifier getOverlay() + { + return m_brain.getOverlay(); + } + + @Override + public ITurtleAccess getAccess() + { + return m_brain; + } + + @Override + public Vec3d getRenderOffset( float f ) + { + return m_brain.getRenderOffset( f ); + } + + @Override + public float getRenderYaw( float f ) + { + return m_brain.getVisualYaw( f ); + } + + @Override + public float getToolRenderAngle( TurtleSide side, float f ) + { + return m_brain.getToolRenderAngle( side, f ); + } + + public void setOwningPlayer( GameProfile player ) + { + m_brain.setOwningPlayer( player ); + markDirty(); + } + + // IInventory + + @Override + public int size() + { + return INVENTORY_SIZE; + } + + @Override + public boolean isEmpty() + { + for( ItemStack stack : m_inventory ) + { + if( !stack.isEmpty() ) return false; + } + return true; + } + + @Nonnull + @Override + public ItemStack getStack( int slot ) + { + if( slot >= 0 && slot < INVENTORY_SIZE ) + { + synchronized( m_inventory ) + { + return m_inventory.get( slot ); + } + } + return ItemStack.EMPTY; + } + + @Nonnull + @Override + public ItemStack removeStack( int slot ) + { + synchronized( m_inventory ) + { + ItemStack result = getStack( slot ); + setStack( slot, ItemStack.EMPTY ); + return result; + } + } + + @Nonnull + @Override + public ItemStack removeStack( int slot, int count ) + { + if( count == 0 ) + { + return ItemStack.EMPTY; + } + + synchronized( m_inventory ) + { + ItemStack stack = getStack( slot ); + if( stack.isEmpty() ) + { + return ItemStack.EMPTY; + } + + if( stack.getCount() <= count ) + { + setStack( slot, ItemStack.EMPTY ); + return stack; + } + + ItemStack part = stack.split( count ); + onInventoryDefinitelyChanged(); + return part; + } + } + + @Override + public void setStack( int i, @Nonnull ItemStack stack ) + { + if( i >= 0 && i < INVENTORY_SIZE ) + { + synchronized( m_inventory ) + { + if( !InventoryUtil.areItemsEqual( stack, m_inventory.get( i ) ) ) + { + m_inventory.set( i, stack ); + onInventoryDefinitelyChanged(); + } + } + } + } + + @Override + public void clear() + { + synchronized( m_inventory ) + { + boolean changed = false; + for( int i = 0; i < INVENTORY_SIZE; i++ ) + { + if( !m_inventory.get( i ).isEmpty() ) + { + m_inventory.set( i, ItemStack.EMPTY ); + changed = true; + } + } + if( changed ) + { + onInventoryDefinitelyChanged(); + } + } + } + + @Override + public void markDirty() + { + super.markDirty(); + synchronized( m_inventory ) + { + if( !m_inventoryChanged ) + { + for( int n = 0; n < size(); n++ ) + { + if( !ItemStack.areEqual( getStack( n ), m_previousInventory.get( n ) ) ) + { + m_inventoryChanged = true; + break; + } + } + } + } + } + + @Override + public boolean canPlayerUse( @Nonnull PlayerEntity player ) + { + return isUsable( player, false ); + } + + private void onInventoryDefinitelyChanged() + { + super.markDirty(); + m_inventoryChanged = true; + } + + public void onTileEntityChange() + { + super.markDirty(); + } + + // Networking stuff + + @Override + protected void writeDescription( @Nonnull CompoundTag nbt ) + { + super.writeDescription( nbt ); + m_brain.writeDescription( nbt ); + } + + @Override + protected void readDescription( @Nonnull CompoundTag nbt ) + { + super.readDescription( nbt ); + m_brain.readDescription( nbt ); + updateBlock(); + } + + // Privates + + private boolean hasPeripheralUpgradeOnSide( ComputerSide side ) + { + ITurtleUpgrade upgrade; + switch( side ) + { + case RIGHT: + upgrade = getUpgrade( TurtleSide.Right ); + break; + case LEFT: + upgrade = getUpgrade( TurtleSide.Left ); + break; + default: + return false; + } + return upgrade != null && upgrade.getType().isPeripheral(); + } + + public void transferStateFrom( TileTurtle copy ) + { + super.transferStateFrom( copy ); + m_inventory = copy.m_inventory; + m_previousInventory = copy.m_previousInventory; + m_inventoryChanged = copy.m_inventoryChanged; + m_brain = copy.m_brain; + m_brain.setOwner( this ); + copy.m_moveState = MoveState.MOVED; + } + + @Nullable + @Override + public IPeripheral getPeripheral( @Nonnull Direction side ) + { + return hasMoved() ? null : new ComputerPeripheral( "turtle", createProxy() ); + } +} diff --git a/remappedSrc/dan200/computercraft/shared/turtle/core/InteractDirection.java b/remappedSrc/dan200/computercraft/shared/turtle/core/InteractDirection.java new file mode 100644 index 000000000..7555b441e --- /dev/null +++ b/remappedSrc/dan200/computercraft/shared/turtle/core/InteractDirection.java @@ -0,0 +1,31 @@ +/* + * 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.shared.turtle.core; + +import dan200.computercraft.api.turtle.ITurtleAccess; +import net.minecraft.util.math.Direction; + +public enum InteractDirection +{ + Forward, + Up, + Down; + + public Direction toWorldDir( ITurtleAccess turtle ) + { + switch( this ) + { + case Forward: + default: + return turtle.getDirection(); + case Up: + return Direction.UP; + case Down: + return Direction.DOWN; + } + } +} diff --git a/remappedSrc/dan200/computercraft/shared/turtle/core/MoveDirection.java b/remappedSrc/dan200/computercraft/shared/turtle/core/MoveDirection.java new file mode 100644 index 000000000..a663dbf53 --- /dev/null +++ b/remappedSrc/dan200/computercraft/shared/turtle/core/MoveDirection.java @@ -0,0 +1,34 @@ +/* + * 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.shared.turtle.core; + +import dan200.computercraft.api.turtle.ITurtleAccess; +import net.minecraft.util.math.Direction; + +public enum MoveDirection +{ + Forward, + Back, + Up, + Down; + + public Direction toWorldDir( ITurtleAccess turtle ) + { + switch( this ) + { + case Forward: + default: + return turtle.getDirection(); + case Back: + return turtle.getDirection().getOpposite(); + case Up: + return Direction.UP; + case Down: + return Direction.DOWN; + } + } +} diff --git a/remappedSrc/dan200/computercraft/shared/turtle/core/TurnDirection.java b/remappedSrc/dan200/computercraft/shared/turtle/core/TurnDirection.java new file mode 100644 index 000000000..caa1ad4ed --- /dev/null +++ b/remappedSrc/dan200/computercraft/shared/turtle/core/TurnDirection.java @@ -0,0 +1,13 @@ +/* + * 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.shared.turtle.core; + +public enum TurnDirection +{ + Left, + Right, +} diff --git a/remappedSrc/dan200/computercraft/shared/turtle/core/TurtleBrain.java b/remappedSrc/dan200/computercraft/shared/turtle/core/TurtleBrain.java new file mode 100644 index 000000000..5c1353eb0 --- /dev/null +++ b/remappedSrc/dan200/computercraft/shared/turtle/core/TurtleBrain.java @@ -0,0 +1,950 @@ +/* + * 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.shared.turtle.core; + +import com.google.common.base.Objects; +import com.mojang.authlib.GameProfile; +import dan200.computercraft.ComputerCraft; +import dan200.computercraft.api.lua.ILuaContext; +import dan200.computercraft.api.lua.LuaException; +import dan200.computercraft.api.peripheral.IPeripheral; +import dan200.computercraft.api.turtle.*; +import dan200.computercraft.core.computer.ComputerSide; +import dan200.computercraft.shared.TurtleUpgrades; +import dan200.computercraft.shared.computer.blocks.ComputerProxy; +import dan200.computercraft.shared.computer.blocks.TileComputerBase; +import dan200.computercraft.shared.computer.core.ComputerFamily; +import dan200.computercraft.shared.computer.core.ServerComputer; +import dan200.computercraft.shared.turtle.blocks.TileTurtle; +import dan200.computercraft.shared.util.Colour; +import dan200.computercraft.shared.util.Holiday; +import dan200.computercraft.shared.util.HolidayUtil; +import dan200.computercraft.shared.util.NBTUtil; +import net.minecraft.block.Block; +import net.minecraft.block.BlockState; +import net.minecraft.block.entity.BlockEntity; +import net.minecraft.entity.Entity; +import net.minecraft.entity.MovementType; +import net.minecraft.fluid.FluidState; +import net.minecraft.inventory.Inventory; +import net.minecraft.nbt.CompoundTag; +import net.minecraft.particle.ParticleTypes; +import net.minecraft.predicate.entity.EntityPredicates; +import net.minecraft.tag.FluidTags; +import net.minecraft.util.DyeColor; +import net.minecraft.util.Identifier; +import net.minecraft.util.math.BlockPos; +import net.minecraft.util.math.Box; +import net.minecraft.util.math.Direction; +import net.minecraft.util.math.Vec3d; +import net.minecraft.world.World; + +import javax.annotation.Nonnull; +import java.util.*; +import java.util.concurrent.TimeUnit; + +import static dan200.computercraft.shared.common.IColouredItem.NBT_COLOUR; +import static dan200.computercraft.shared.util.WaterloggableBlock.WATERLOGGED; + +public class TurtleBrain implements ITurtleAccess +{ + public static final String NBT_RIGHT_UPGRADE = "RightUpgrade"; + public static final String NBT_RIGHT_UPGRADE_DATA = "RightUpgradeNbt"; + public static final String NBT_LEFT_UPGRADE = "LeftUpgrade"; + public static final String NBT_LEFT_UPGRADE_DATA = "LeftUpgradeNbt"; + public static final String NBT_FUEL = "Fuel"; + public static final String NBT_OVERLAY = "Overlay"; + + private static final String NBT_SLOT = "Slot"; + + private static final int ANIM_DURATION = 8; + + private TileTurtle m_owner; + private ComputerProxy m_proxy; + private GameProfile m_owningPlayer; + + private Queue m_commandQueue = new ArrayDeque<>(); + private int m_commandsIssued = 0; + + private Map m_upgrades = new EnumMap<>( TurtleSide.class ); + private Map peripherals = new EnumMap<>( TurtleSide.class ); + private Map m_upgradeNBTData = new EnumMap<>( TurtleSide.class ); + + private int m_selectedSlot = 0; + private int m_fuelLevel = 0; + private int m_colourHex = -1; + private Identifier m_overlay = null; + + private TurtleAnimation m_animation = TurtleAnimation.None; + private int m_animationProgress = 0; + private int m_lastAnimationProgress = 0; + + TurtlePlayer m_cachedPlayer; + + public TurtleBrain( TileTurtle turtle ) + { + m_owner = turtle; + } + + public void setOwner( TileTurtle owner ) + { + m_owner = owner; + } + + public TileTurtle getOwner() + { + return m_owner; + } + + public ComputerProxy getProxy() + { + if( m_proxy == null ) + { + m_proxy = new ComputerProxy() + { + @Override + protected TileComputerBase getTile() + { + return m_owner; + } + }; + } + return m_proxy; + } + + public ComputerFamily getFamily() + { + return m_owner.getFamily(); + } + + public void setupComputer( ServerComputer computer ) + { + updatePeripherals( computer ); + } + + public void update() + { + World world = getWorld(); + if( !world.isClient ) + { + // Advance movement + updateCommands(); + } + + // Advance animation + updateAnimation(); + + // Advance upgrades + if( !m_upgrades.isEmpty() ) + { + for( Map.Entry entry : m_upgrades.entrySet() ) + { + entry.getValue().update( this, entry.getKey() ); + } + } + } + + /** + * Read common data for saving and client synchronisation + * + * @param nbt The tag to read from + */ + private void readCommon( CompoundTag nbt ) + { + // Read fields + m_colourHex = nbt.contains( NBT_COLOUR ) ? nbt.getInt( NBT_COLOUR ) : -1; + m_fuelLevel = nbt.contains( NBT_FUEL ) ? nbt.getInt( NBT_FUEL ) : 0; + m_overlay = nbt.contains( NBT_OVERLAY ) ? new Identifier( nbt.getString( NBT_OVERLAY ) ) : null; + + // Read upgrades + setUpgrade( TurtleSide.Left, nbt.contains( NBT_LEFT_UPGRADE ) ? TurtleUpgrades.get( nbt.getString( NBT_LEFT_UPGRADE ) ) : null ); + setUpgrade( TurtleSide.Right, nbt.contains( NBT_RIGHT_UPGRADE ) ? TurtleUpgrades.get( nbt.getString( NBT_RIGHT_UPGRADE ) ) : null ); + + // NBT + m_upgradeNBTData.clear(); + if( nbt.contains( NBT_LEFT_UPGRADE_DATA ) ) + { + m_upgradeNBTData.put( TurtleSide.Left, nbt.getCompound( NBT_LEFT_UPGRADE_DATA ).copy() ); + } + if( nbt.contains( NBT_RIGHT_UPGRADE_DATA ) ) + { + m_upgradeNBTData.put( TurtleSide.Right, nbt.getCompound( NBT_RIGHT_UPGRADE_DATA ).copy() ); + } + } + + private void writeCommon( CompoundTag nbt ) + { + nbt.putInt( NBT_FUEL, m_fuelLevel ); + if( m_colourHex != -1 ) nbt.putInt( NBT_COLOUR, m_colourHex ); + if( m_overlay != null ) nbt.putString( NBT_OVERLAY, m_overlay.toString() ); + + // Write upgrades + String leftUpgradeId = getUpgradeId( getUpgrade( TurtleSide.Left ) ); + if( leftUpgradeId != null ) nbt.putString( NBT_LEFT_UPGRADE, leftUpgradeId ); + String rightUpgradeId = getUpgradeId( getUpgrade( TurtleSide.Right ) ); + if( rightUpgradeId != null ) nbt.putString( NBT_RIGHT_UPGRADE, rightUpgradeId ); + + // Write upgrade NBT + if( m_upgradeNBTData.containsKey( TurtleSide.Left ) ) + { + nbt.put( NBT_LEFT_UPGRADE_DATA, getUpgradeNBTData( TurtleSide.Left ).copy() ); + } + if( m_upgradeNBTData.containsKey( TurtleSide.Right ) ) + { + nbt.put( NBT_RIGHT_UPGRADE_DATA, getUpgradeNBTData( TurtleSide.Right ).copy() ); + } + } + + public void readFromNBT( CompoundTag nbt ) + { + readCommon( nbt ); + + // Read state + m_selectedSlot = nbt.getInt( NBT_SLOT ); + + // Read owner + if( nbt.contains( "Owner", NBTUtil.TAG_COMPOUND ) ) + { + CompoundTag owner = nbt.getCompound( "Owner" ); + m_owningPlayer = new GameProfile( + new UUID( owner.getLong( "UpperId" ), owner.getLong( "LowerId" ) ), + owner.getString( "Name" ) + ); + } + else + { + m_owningPlayer = null; + } + } + + public CompoundTag writeToNBT( CompoundTag nbt ) + { + writeCommon( nbt ); + + // Write state + nbt.putInt( NBT_SLOT, m_selectedSlot ); + + // Write owner + if( m_owningPlayer != null ) + { + CompoundTag owner = new CompoundTag(); + nbt.put( "Owner", owner ); + + owner.putLong( "UpperId", m_owningPlayer.getId().getMostSignificantBits() ); + owner.putLong( "LowerId", m_owningPlayer.getId().getLeastSignificantBits() ); + owner.putString( "Name", m_owningPlayer.getName() ); + } + + return nbt; + } + + private static String getUpgradeId( ITurtleUpgrade upgrade ) + { + return upgrade != null ? upgrade.getUpgradeID().toString() : null; + } + + public void readDescription( CompoundTag nbt ) + { + readCommon( nbt ); + + // Animation + TurtleAnimation anim = TurtleAnimation.values()[nbt.getInt( "Animation" )]; + if( anim != m_animation && + anim != TurtleAnimation.Wait && + anim != TurtleAnimation.ShortWait && + anim != TurtleAnimation.None ) + { + m_animation = anim; + m_animationProgress = 0; + m_lastAnimationProgress = 0; + } + } + + public void writeDescription( CompoundTag nbt ) + { + writeCommon( nbt ); + nbt.putInt( "Animation", m_animation.ordinal() ); + } + + @Nonnull + @Override + public World getWorld() + { + return m_owner.getWorld(); + } + + @Nonnull + @Override + public BlockPos getPosition() + { + return m_owner.getPos(); + } + + @Override + public boolean teleportTo( @Nonnull World world, @Nonnull BlockPos pos ) + { + if( world.isClient || getWorld().isClient ) + { + throw new UnsupportedOperationException( "Cannot teleport on the client" ); + } + + // Cache info about the old turtle (so we don't access this after we delete ourselves) + World oldWorld = getWorld(); + TileTurtle oldOwner = m_owner; + BlockPos oldPos = m_owner.getPos(); + BlockState oldBlock = m_owner.getCachedState(); + + if( oldWorld == world && oldPos.equals( pos ) ) + { + // Teleporting to the current position is a no-op + return true; + } + + // Ensure the chunk is loaded + if( !world.isBlockLoaded( pos ) ) return false; + + // Ensure we're inside the world border + if( !world.getWorldBorder().contains( pos ) ) return false; + + FluidState existingFluid = world.getBlockState( pos ).getFluidState(); + BlockState newState = oldBlock + // We only mark this as waterlogged when travelling into a source block. This prevents us from spreading + // fluid by creating a new source when moving into a block, causing the next block to be almost full and + // then moving into that. + .with( WATERLOGGED, existingFluid.isIn( FluidTags.WATER ) && existingFluid.isStill() ); + + oldOwner.notifyMoveStart(); + + try + { + // Create a new turtle + if( world.setBlockState( pos, newState, 0 ) ) + { + Block block = world.getBlockState( pos ).getBlock(); + if( block == oldBlock.getBlock() ) + { + BlockEntity newTile = world.getBlockEntity( pos ); + if( newTile instanceof TileTurtle ) + { + // Copy the old turtle state into the new turtle + TileTurtle newTurtle = (TileTurtle) newTile; + newTurtle.setLocation( world ); + newTurtle.setPos( pos ); + newTurtle.transferStateFrom( oldOwner ); + newTurtle.createServerComputer().setWorld( world ); + newTurtle.createServerComputer().setPosition( pos ); + + // Remove the old turtle + oldWorld.removeBlock( oldPos, false ); + + // Make sure everybody knows about it + newTurtle.updateBlock(); + newTurtle.updateInput(); + newTurtle.updateOutput(); + return true; + } + } + + // Something went wrong, remove the newly created turtle + world.removeBlock( pos, false ); + } + } + finally + { + // whatever happens, unblock old turtle in case it's still in world + oldOwner.notifyMoveEnd(); + } + + return false; + } + + @Nonnull + @Override + public Vec3d getVisualPosition( float f ) + { + Vec3d offset = getRenderOffset( f ); + BlockPos pos = m_owner.getPos(); + return new Vec3d( + pos.getX() + 0.5 + offset.x, + pos.getY() + 0.5 + offset.y, + pos.getZ() + 0.5 + offset.z + ); + } + + @Override + public float getVisualYaw( float f ) + { + float yaw = getDirection().asRotation(); + switch( m_animation ) + { + case TurnLeft: + { + yaw += 90.0f * (1.0f - getAnimationFraction( f )); + if( yaw >= 360.0f ) + { + yaw -= 360.0f; + } + break; + } + case TurnRight: + { + yaw += -90.0f * (1.0f - getAnimationFraction( f )); + if( yaw < 0.0f ) + { + yaw += 360.0f; + } + break; + } + } + return yaw; + } + + @Nonnull + @Override + public Direction getDirection() + { + return m_owner.getDirection(); + } + + @Override + public void setDirection( @Nonnull Direction dir ) + { + m_owner.setDirection( dir ); + } + + @Override + public int getSelectedSlot() + { + return m_selectedSlot; + } + + @Override + public void setSelectedSlot( int slot ) + { + if( getWorld().isClient ) throw new UnsupportedOperationException( "Cannot set the slot on the client" ); + + if( slot >= 0 && slot < m_owner.size() ) + { + m_selectedSlot = slot; + m_owner.onTileEntityChange(); + } + } + + @Nonnull + @Override + public Inventory getInventory() + { + return m_owner; + } + + /* + @Nonnull + @Override + public IItemHandlerModifiable getItemHandler() + { + return m_owner.getItemHandler(); + } + */ + + @Override + public boolean isFuelNeeded() + { + return ComputerCraft.turtlesNeedFuel; + } + + @Override + public int getFuelLevel() + { + return Math.min( m_fuelLevel, getFuelLimit() ); + } + + @Override + public void setFuelLevel( int level ) + { + m_fuelLevel = Math.min( level, getFuelLimit() ); + m_owner.onTileEntityChange(); + } + + @Override + public int getFuelLimit() + { + if( m_owner.getFamily() == ComputerFamily.Advanced ) + { + return ComputerCraft.advancedTurtleFuelLimit; + } + else + { + return ComputerCraft.turtleFuelLimit; + } + } + + @Override + public boolean consumeFuel( int fuel ) + { + if( getWorld().isClient ) throw new UnsupportedOperationException( "Cannot consume fuel on the client" ); + + if( !isFuelNeeded() ) return true; + + int consumption = Math.max( fuel, 0 ); + if( getFuelLevel() >= consumption ) + { + setFuelLevel( getFuelLevel() - consumption ); + return true; + } + return false; + } + + @Override + public void addFuel( int fuel ) + { + if( getWorld().isClient ) throw new UnsupportedOperationException( "Cannot add fuel on the client" ); + int addition = Math.max( fuel, 0 ); + setFuelLevel( getFuelLevel() + addition ); + } + + private int issueCommand( ITurtleCommand command ) + { + m_commandQueue.offer( new TurtleCommandQueueEntry( ++m_commandsIssued, command ) ); + return m_commandsIssued; + } + + @Nonnull + @Override + public Object[] executeCommand( @Nonnull ILuaContext context, @Nonnull ITurtleCommand command ) throws LuaException, InterruptedException + { + if( getWorld().isClient ) throw new UnsupportedOperationException( "Cannot run commands on the client" ); + + // Issue command + int commandID = issueCommand( command ); + + // Wait for response + while( true ) + { + Object[] response = context.pullEvent( "turtle_response" ); + if( response.length >= 3 && response[1] instanceof Number && response[2] instanceof Boolean ) + { + if( ((Number) response[1]).intValue() == commandID ) + { + Object[] returnValues = new Object[response.length - 2]; + System.arraycopy( response, 2, returnValues, 0, returnValues.length ); + return returnValues; + } + } + } + } + + @Override + public void playAnimation( @Nonnull TurtleAnimation animation ) + { + if( getWorld().isClient ) throw new UnsupportedOperationException( "Cannot play animations on the client" ); + m_animation = animation; + if( m_animation == TurtleAnimation.ShortWait ) + { + m_animationProgress = ANIM_DURATION / 2; + m_lastAnimationProgress = ANIM_DURATION / 2; + } + else + { + m_animationProgress = 0; + m_lastAnimationProgress = 0; + } + m_owner.updateBlock(); + } + + public Identifier getOverlay() + { + return m_overlay; + } + + public void setOverlay( Identifier overlay ) + { + if( !Objects.equal( m_overlay, overlay ) ) + { + m_overlay = overlay; + m_owner.updateBlock(); + } + } + + public DyeColor getDyeColour() + { + if( m_colourHex == -1 ) return null; + Colour colour = Colour.fromHex( m_colourHex ); + return colour == null ? null : DyeColor.byId( 15 - colour.ordinal() ); + } + + public void setDyeColour( DyeColor dyeColour ) + { + int newColour = -1; + if( dyeColour != null ) + { + newColour = Colour.values()[15 - dyeColour.getId()].getHex(); + } + if( m_colourHex != newColour ) + { + m_colourHex = newColour; + m_owner.updateBlock(); + } + } + + @Override + public void setColour( int colour ) + { + if( colour >= 0 && colour <= 0xFFFFFF ) + { + if( m_colourHex != colour ) + { + m_colourHex = colour; + m_owner.updateBlock(); + } + } + else if( m_colourHex != -1 ) + { + m_colourHex = -1; + m_owner.updateBlock(); + } + } + + @Override + public int getColour() + { + return m_colourHex; + } + + public void setOwningPlayer( GameProfile profile ) + { + m_owningPlayer = profile; + } + + @Nonnull + @Override + public GameProfile getOwningPlayer() + { + return m_owningPlayer; + } + + @Override + public ITurtleUpgrade getUpgrade( @Nonnull TurtleSide side ) + { + return m_upgrades.get( side ); + } + + @Override + public void setUpgrade( @Nonnull TurtleSide side, ITurtleUpgrade upgrade ) + { + // Remove old upgrade + if( m_upgrades.containsKey( side ) ) + { + if( m_upgrades.get( side ) == upgrade ) return; + m_upgrades.remove( side ); + } + else + { + if( upgrade == null ) return; + } + + m_upgradeNBTData.remove( side ); + + // Set new upgrade + if( upgrade != null ) m_upgrades.put( side, upgrade ); + + // Notify clients and create peripherals + if( m_owner.getWorld() != null ) + { + updatePeripherals( m_owner.createServerComputer() ); + m_owner.updateBlock(); + } + } + + @Override + public IPeripheral getPeripheral( @Nonnull TurtleSide side ) + { + return peripherals.get( side ); + } + + @Nonnull + @Override + public CompoundTag getUpgradeNBTData( TurtleSide side ) + { + CompoundTag nbt = m_upgradeNBTData.get( side ); + if( nbt == null ) m_upgradeNBTData.put( side, nbt = new CompoundTag() ); + return nbt; + } + + @Override + public void updateUpgradeNBTData( @Nonnull TurtleSide side ) + { + m_owner.updateBlock(); + } + + public Vec3d getRenderOffset( float f ) + { + switch( m_animation ) + { + case MoveForward: + case MoveBack: + case MoveUp: + case MoveDown: + { + // Get direction + Direction dir; + switch( m_animation ) + { + case MoveForward: + default: + dir = getDirection(); + break; + case MoveBack: + dir = getDirection().getOpposite(); + break; + case MoveUp: + dir = Direction.UP; + break; + case MoveDown: + dir = Direction.DOWN; + break; + } + + double distance = -1.0 + getAnimationFraction( f ); + return new Vec3d( + distance * dir.getOffsetX(), + distance * dir.getOffsetY(), + distance * dir.getOffsetZ() + ); + } + default: + { + return Vec3d.ZERO; + } + } + } + + public float getToolRenderAngle( TurtleSide side, float f ) + { + return (side == TurtleSide.Left && m_animation == TurtleAnimation.SwingLeftTool) || + (side == TurtleSide.Right && m_animation == TurtleAnimation.SwingRightTool) + ? 45.0f * (float) Math.sin( getAnimationFraction( f ) * Math.PI ) + : 0.0f; + } + + private static ComputerSide toDirection( TurtleSide side ) + { + switch( side ) + { + case Left: + return ComputerSide.LEFT; + case Right: + default: + return ComputerSide.RIGHT; + } + } + + private void updatePeripherals( ServerComputer serverComputer ) + { + if( serverComputer == null ) return; + + // Update peripherals + for( TurtleSide side : TurtleSide.values() ) + { + ITurtleUpgrade upgrade = getUpgrade( side ); + IPeripheral peripheral = null; + if( upgrade != null && upgrade.getType().isPeripheral() ) + { + peripheral = upgrade.createPeripheral( this, side ); + } + + IPeripheral existing = peripherals.get( side ); + if( existing == peripheral || (existing != null && peripheral != null && existing.equals( peripheral )) ) + { + // If the peripheral is the same, just use that. + peripheral = existing; + } + else + { + // Otherwise update our map + peripherals.put( side, peripheral ); + } + + // Always update the computer: it may not be the same computer as before! + serverComputer.setPeripheral( toDirection( side ), peripheral ); + } + } + + private void updateCommands() + { + if( m_animation != TurtleAnimation.None || m_commandQueue.isEmpty() ) return; + + // If we've got a computer, ensure that we're allowed to perform work. + ServerComputer computer = m_owner.getServerComputer(); + if( computer != null && !computer.getComputer().getMainThreadMonitor().canWork() ) return; + + // Pull a new command + TurtleCommandQueueEntry nextCommand = m_commandQueue.poll(); + if( nextCommand == null ) return; + + // Execute the command + long start = System.nanoTime(); + TurtleCommandResult result = nextCommand.command.execute( this ); + long end = System.nanoTime(); + + // Dispatch the callback + if( computer == null ) return; + computer.getComputer().getMainThreadMonitor().trackWork( end - start, TimeUnit.NANOSECONDS ); + int callbackID = nextCommand.callbackID; + if( callbackID < 0 ) return; + + if( result != null && result.isSuccess() ) + { + Object[] results = result.getResults(); + if( results != null ) + { + Object[] arguments = new Object[results.length + 2]; + arguments[0] = callbackID; + arguments[1] = true; + System.arraycopy( results, 0, arguments, 2, results.length ); + computer.queueEvent( "turtle_response", arguments ); + } + else + { + computer.queueEvent( "turtle_response", new Object[] { + callbackID, true + } ); + } + } + else + { + computer.queueEvent( "turtle_response", new Object[] { + callbackID, false, result != null ? result.getErrorMessage() : null + } ); + } + } + + private void updateAnimation() + { + if( m_animation != TurtleAnimation.None ) + { + World world = getWorld(); + + if( ComputerCraft.turtlesCanPush ) + { + // Advance entity pushing + if( m_animation == TurtleAnimation.MoveForward || + m_animation == TurtleAnimation.MoveBack || + m_animation == TurtleAnimation.MoveUp || + m_animation == TurtleAnimation.MoveDown ) + { + BlockPos pos = getPosition(); + Direction moveDir; + switch( m_animation ) + { + case MoveForward: + default: + moveDir = getDirection(); + break; + case MoveBack: + moveDir = getDirection().getOpposite(); + break; + case MoveUp: + moveDir = Direction.UP; + break; + case MoveDown: + moveDir = Direction.DOWN; + break; + } + + double minX = pos.getX(); + double minY = pos.getY(); + double minZ = pos.getZ(); + double maxX = minX + 1.0; + double maxY = minY + 1.0; + double maxZ = minZ + 1.0; + + float pushFrac = 1.0f - (float) (m_animationProgress + 1) / ANIM_DURATION; + float push = Math.max( pushFrac + 0.0125f, 0.0f ); + if( moveDir.getOffsetX() < 0 ) + { + minX += moveDir.getOffsetX() * push; + } + else + { + maxX -= moveDir.getOffsetX() * push; + } + + if( moveDir.getOffsetY() < 0 ) + { + minY += moveDir.getOffsetY() * push; + } + else + { + maxY -= moveDir.getOffsetY() * push; + } + + if( moveDir.getOffsetZ() < 0 ) + { + minZ += moveDir.getOffsetZ() * push; + } + else + { + maxZ -= moveDir.getOffsetZ() * push; + } + + Box aabb = new Box( minX, minY, minZ, maxX, maxY, maxZ ); + List list = world.getEntities( (Entity) null, aabb, EntityPredicates.EXCEPT_SPECTATOR ); + if( !list.isEmpty() ) + { + double pushStep = 1.0f / ANIM_DURATION; + double pushStepX = moveDir.getOffsetX() * pushStep; + double pushStepY = moveDir.getOffsetY() * pushStep; + double pushStepZ = moveDir.getOffsetZ() * pushStep; + for( Entity entity : list ) + { + entity.move( MovementType.PISTON, new Vec3d( pushStepX, pushStepY, pushStepZ ) ); + } + } + } + } + + // Advance valentines day easter egg + if( world.isClient && m_animation == TurtleAnimation.MoveForward && m_animationProgress == 4 ) + { + // Spawn love pfx if valentines day + Holiday currentHoliday = HolidayUtil.getCurrentHoliday(); + if( currentHoliday == Holiday.Valentines ) + { + Vec3d position = getVisualPosition( 1.0f ); + if( position != null ) + { + double x = position.x + world.random.nextGaussian() * 0.1; + double y = position.y + 0.5 + world.random.nextGaussian() * 0.1; + double z = position.z + world.random.nextGaussian() * 0.1; + world.addParticle( + ParticleTypes.HEART, x, y, z, + world.random.nextGaussian() * 0.02, + world.random.nextGaussian() * 0.02, + world.random.nextGaussian() * 0.02 + ); + } + } + } + + // Wait for anim completion + m_lastAnimationProgress = m_animationProgress; + if( ++m_animationProgress >= ANIM_DURATION ) + { + m_animation = TurtleAnimation.None; + m_animationProgress = 0; + m_lastAnimationProgress = 0; + } + } + } + + private float getAnimationFraction( float f ) + { + float next = (float) m_animationProgress / ANIM_DURATION; + float previous = (float) m_lastAnimationProgress / ANIM_DURATION; + return previous + (next - previous) * f; + } +} diff --git a/remappedSrc/dan200/computercraft/shared/turtle/core/TurtleCommandQueueEntry.java b/remappedSrc/dan200/computercraft/shared/turtle/core/TurtleCommandQueueEntry.java new file mode 100644 index 000000000..d3f20ac59 --- /dev/null +++ b/remappedSrc/dan200/computercraft/shared/turtle/core/TurtleCommandQueueEntry.java @@ -0,0 +1,21 @@ +/* + * 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.shared.turtle.core; + +import dan200.computercraft.api.turtle.ITurtleCommand; + +public class TurtleCommandQueueEntry +{ + public final int callbackID; + public final ITurtleCommand command; + + public TurtleCommandQueueEntry( int callbackID, ITurtleCommand command ) + { + this.callbackID = callbackID; + this.command = command; + } +} diff --git a/remappedSrc/dan200/computercraft/shared/turtle/core/TurtleCompareCommand.java b/remappedSrc/dan200/computercraft/shared/turtle/core/TurtleCompareCommand.java new file mode 100644 index 000000000..4dfbcb2d2 --- /dev/null +++ b/remappedSrc/dan200/computercraft/shared/turtle/core/TurtleCompareCommand.java @@ -0,0 +1,85 @@ +/* + * 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.shared.turtle.core; + +import dan200.computercraft.api.turtle.ITurtleAccess; +import dan200.computercraft.api.turtle.ITurtleCommand; +import dan200.computercraft.api.turtle.TurtleCommandResult; +import net.minecraft.block.Block; +import net.minecraft.block.BlockState; +import net.minecraft.item.ItemStack; +import net.minecraft.server.world.ServerWorld; +import net.minecraft.util.math.BlockPos; +import net.minecraft.util.math.Direction; +import net.minecraft.world.World; + +import javax.annotation.Nonnull; +import java.util.List; + +public class TurtleCompareCommand implements ITurtleCommand +{ + private final InteractDirection m_direction; + + public TurtleCompareCommand( InteractDirection direction ) + { + m_direction = direction; + } + + @Nonnull + @Override + public TurtleCommandResult execute( @Nonnull ITurtleAccess turtle ) + { + // Get world direction from direction + Direction direction = m_direction.toWorldDir( turtle ); + + // Get currently selected stack + ItemStack selectedStack = turtle.getInventory().getStack( turtle.getSelectedSlot() ); + + // Get stack representing thing in front + World world = turtle.getWorld(); + BlockPos oldPosition = turtle.getPosition(); + BlockPos newPosition = oldPosition.offset( direction ); + + ItemStack lookAtStack = ItemStack.EMPTY; + if( !world.isAir( newPosition ) ) + { + BlockState lookAtState = world.getBlockState( newPosition ); + Block lookAtBlock = lookAtState.getBlock(); + if( !lookAtState.isAir() ) + { + // See if the block drops anything with the same ID as itself + // (try 5 times to try and beat random number generators) + for( int i = 0; i < 5 && lookAtStack.isEmpty(); i++ ) + { + List drops = Block.getDroppedStacks( lookAtState, (ServerWorld) world, newPosition, world.getBlockEntity( newPosition ) ); + if( !drops.isEmpty() ) + { + for( ItemStack drop : drops ) + { + if( drop.getItem() == lookAtBlock.asItem() ) + { + lookAtStack = drop; + break; + } + } + } + } + + // Last resort: roll our own (which will probably be wrong) + if( lookAtStack.isEmpty() ) + { + lookAtStack = new ItemStack( lookAtBlock ); + } + } + } + + // Compare them + return selectedStack.getItem() == lookAtStack.getItem() + ? TurtleCommandResult.success() + : TurtleCommandResult.failure(); + } +} diff --git a/remappedSrc/dan200/computercraft/shared/turtle/core/TurtleCompareToCommand.java b/remappedSrc/dan200/computercraft/shared/turtle/core/TurtleCompareToCommand.java new file mode 100644 index 000000000..a91d5b59f --- /dev/null +++ b/remappedSrc/dan200/computercraft/shared/turtle/core/TurtleCompareToCommand.java @@ -0,0 +1,41 @@ +/* + * 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.shared.turtle.core; + +import dan200.computercraft.api.turtle.ITurtleAccess; +import dan200.computercraft.api.turtle.ITurtleCommand; +import dan200.computercraft.api.turtle.TurtleCommandResult; +import dan200.computercraft.shared.util.InventoryUtil; +import net.minecraft.item.ItemStack; + +import javax.annotation.Nonnull; + +public class TurtleCompareToCommand implements ITurtleCommand +{ + private final int m_slot; + + public TurtleCompareToCommand( int slot ) + { + m_slot = slot; + } + + @Nonnull + @Override + public TurtleCommandResult execute( @Nonnull ITurtleAccess turtle ) + { + ItemStack selectedStack = turtle.getInventory().getStack( turtle.getSelectedSlot() ); + ItemStack stack = turtle.getInventory().getStack( m_slot ); + if( InventoryUtil.areItemsStackable( selectedStack, stack ) ) + { + return TurtleCommandResult.success(); + } + else + { + return TurtleCommandResult.failure(); + } + } +} diff --git a/remappedSrc/dan200/computercraft/shared/turtle/core/TurtleCraftCommand.java b/remappedSrc/dan200/computercraft/shared/turtle/core/TurtleCraftCommand.java new file mode 100644 index 000000000..d8472a8a9 --- /dev/null +++ b/remappedSrc/dan200/computercraft/shared/turtle/core/TurtleCraftCommand.java @@ -0,0 +1,54 @@ +/* + * 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.shared.turtle.core; + +import dan200.computercraft.api.turtle.ITurtleAccess; +import dan200.computercraft.api.turtle.ITurtleCommand; +import dan200.computercraft.api.turtle.TurtleAnimation; +import dan200.computercraft.api.turtle.TurtleCommandResult; +import dan200.computercraft.shared.turtle.upgrades.TurtleInventoryCrafting; +import dan200.computercraft.shared.util.InventoryUtil; +import dan200.computercraft.shared.util.ItemStorage; +import dan200.computercraft.shared.util.WorldUtil; +import net.minecraft.item.ItemStack; + +import javax.annotation.Nonnull; +import java.util.List; + +public class TurtleCraftCommand implements ITurtleCommand +{ + private final int limit; + + public TurtleCraftCommand( int limit ) + { + this.limit = limit; + } + + @Nonnull + @Override + public TurtleCommandResult execute( @Nonnull ITurtleAccess turtle ) + { + // Craft the item + TurtleInventoryCrafting crafting = new TurtleInventoryCrafting( turtle ); + List results = crafting.doCrafting( turtle.getWorld(), limit ); + if( results == null ) return TurtleCommandResult.failure( "No matching recipes" ); + + // Store or drop any remainders + ItemStorage storage = ItemStorage.wrap( turtle.getInventory() ); + for( ItemStack stack : results ) + { + ItemStack remainder = InventoryUtil.storeItems( stack, storage, turtle.getSelectedSlot() ); + if( !remainder.isEmpty() ) + { + WorldUtil.dropItemStack( remainder, turtle.getWorld(), turtle.getPosition(), turtle.getDirection() ); + } + } + + if( !results.isEmpty() ) turtle.playAnimation( TurtleAnimation.Wait ); + return TurtleCommandResult.success(); + } +} diff --git a/remappedSrc/dan200/computercraft/shared/turtle/core/TurtleDetectCommand.java b/remappedSrc/dan200/computercraft/shared/turtle/core/TurtleDetectCommand.java new file mode 100644 index 000000000..47258d6f3 --- /dev/null +++ b/remappedSrc/dan200/computercraft/shared/turtle/core/TurtleDetectCommand.java @@ -0,0 +1,47 @@ +/* + * 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.shared.turtle.core; + +import dan200.computercraft.api.turtle.ITurtleAccess; +import dan200.computercraft.api.turtle.ITurtleCommand; +import dan200.computercraft.api.turtle.TurtleCommandResult; +import dan200.computercraft.shared.util.WorldUtil; +import net.minecraft.util.math.BlockPos; +import net.minecraft.util.math.Direction; +import net.minecraft.world.World; + +import javax.annotation.Nonnull; + +public class TurtleDetectCommand implements ITurtleCommand +{ + private final InteractDirection m_direction; + + public TurtleDetectCommand( InteractDirection direction ) + { + m_direction = direction; + } + + @Nonnull + @Override + public TurtleCommandResult execute( @Nonnull ITurtleAccess turtle ) + { + // Get world direction from direction + Direction direction = m_direction.toWorldDir( turtle ); + + // Check if thing in front is air or not + World world = turtle.getWorld(); + BlockPos oldPosition = turtle.getPosition(); + BlockPos newPosition = oldPosition.offset( direction ); + + if( !WorldUtil.isLiquidBlock( world, newPosition ) && !world.isAir( newPosition ) ) + { + return TurtleCommandResult.success(); + } + + return TurtleCommandResult.failure(); + } +} diff --git a/remappedSrc/dan200/computercraft/shared/turtle/core/TurtleDropCommand.java b/remappedSrc/dan200/computercraft/shared/turtle/core/TurtleDropCommand.java new file mode 100644 index 000000000..f4bf1dd64 --- /dev/null +++ b/remappedSrc/dan200/computercraft/shared/turtle/core/TurtleDropCommand.java @@ -0,0 +1,106 @@ +/* + * 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.shared.turtle.core; + +import dan200.computercraft.api.turtle.ITurtleAccess; +import dan200.computercraft.api.turtle.ITurtleCommand; +import dan200.computercraft.api.turtle.TurtleAnimation; +import dan200.computercraft.api.turtle.TurtleCommandResult; +import dan200.computercraft.api.turtle.event.TurtleEvent; +import dan200.computercraft.api.turtle.event.TurtleInventoryEvent; +import dan200.computercraft.shared.util.InventoryUtil; +import dan200.computercraft.shared.util.ItemStorage; +import dan200.computercraft.shared.util.WorldUtil; +import net.minecraft.inventory.Inventory; +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; + +public class TurtleDropCommand implements ITurtleCommand +{ + private final InteractDirection m_direction; + private final int m_quantity; + + public TurtleDropCommand( InteractDirection direction, int quantity ) + { + m_direction = direction; + m_quantity = quantity; + } + + @Nonnull + @Override + public TurtleCommandResult execute( @Nonnull ITurtleAccess turtle ) + { + // Dropping nothing is easy + if( m_quantity == 0 ) + { + turtle.playAnimation( TurtleAnimation.Wait ); + return TurtleCommandResult.success(); + } + + // Get world direction from direction + Direction direction = m_direction.toWorldDir( turtle ); + ItemStorage storage = ItemStorage.wrap( turtle.getInventory() ); + + // Get things to drop + ItemStack stack = InventoryUtil.takeItems( m_quantity, storage, turtle.getSelectedSlot(), 1, turtle.getSelectedSlot() ); + if( stack.isEmpty() ) + { + return TurtleCommandResult.failure( "No items to drop" ); + } + + // Get inventory for thing in front + World world = turtle.getWorld(); + BlockPos oldPosition = turtle.getPosition(); + BlockPos newPosition = oldPosition.offset( direction ); + Direction side = direction.getOpposite(); + + Inventory inventory = InventoryUtil.getInventory( world, newPosition, side ); + + // Fire the event, restoring the inventory and exiting if it is cancelled. + TurtlePlayer player = TurtlePlaceCommand.createPlayer( turtle, oldPosition, direction ); + TurtleInventoryEvent.Drop event = new TurtleInventoryEvent.Drop( turtle, player, world, newPosition, inventory, stack ); + if( TurtleEvent.post( event ) ) + { + InventoryUtil.storeItems( stack, storage, turtle.getSelectedSlot() ); + return TurtleCommandResult.failure( event.getFailureMessage() ); + } + + if( inventory != null ) + { + // Drop the item into the inventory + ItemStack remainder = InventoryUtil.storeItems( stack, ItemStorage.wrap( inventory, side ) ); + if( !remainder.isEmpty() ) + { + // Put the remainder back in the turtle + InventoryUtil.storeItems( remainder, storage, turtle.getSelectedSlot() ); + } + + // Return true if we stored anything + if( remainder != stack ) + { + turtle.playAnimation( TurtleAnimation.Wait ); + return TurtleCommandResult.success(); + } + else + { + return TurtleCommandResult.failure( "No space for items" ); + } + } + else + { + // Drop the item into the world + WorldUtil.dropItemStack( stack, world, oldPosition, direction ); + world.syncGlobalEvent( 1000, newPosition, 0 ); // BLOCK_DISPENSER_DISPENSE + turtle.playAnimation( TurtleAnimation.Wait ); + return TurtleCommandResult.success(); + } + } +} diff --git a/remappedSrc/dan200/computercraft/shared/turtle/core/TurtleEquipCommand.java b/remappedSrc/dan200/computercraft/shared/turtle/core/TurtleEquipCommand.java new file mode 100644 index 000000000..3b37f6082 --- /dev/null +++ b/remappedSrc/dan200/computercraft/shared/turtle/core/TurtleEquipCommand.java @@ -0,0 +1,103 @@ +/* + * 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.shared.turtle.core; + +import dan200.computercraft.api.turtle.*; +import dan200.computercraft.api.turtle.event.TurtleAction; +import dan200.computercraft.api.turtle.event.TurtleActionEvent; +import dan200.computercraft.api.turtle.event.TurtleEvent; +import dan200.computercraft.shared.TurtleUpgrades; +import dan200.computercraft.shared.util.InventoryUtil; +import dan200.computercraft.shared.util.ItemStorage; +import dan200.computercraft.shared.util.WorldUtil; +import net.minecraft.inventory.Inventory; +import net.minecraft.item.ItemStack; +import net.minecraft.util.math.BlockPos; + +import javax.annotation.Nonnull; + +public class TurtleEquipCommand implements ITurtleCommand +{ + private final TurtleSide m_side; + + public TurtleEquipCommand( TurtleSide side ) + { + m_side = side; + } + + @Nonnull + @Override + public TurtleCommandResult execute( @Nonnull ITurtleAccess turtle ) + { + // Determine the upgrade to equipLeft + ITurtleUpgrade newUpgrade; + ItemStack newUpgradeStack; + Inventory inventory = turtle.getInventory(); + ItemStorage storage = ItemStorage.wrap( turtle.getInventory() ); + ItemStack selectedStack = inventory.getStack( turtle.getSelectedSlot() ); + if( !selectedStack.isEmpty() ) + { + newUpgradeStack = selectedStack.copy(); + newUpgrade = TurtleUpgrades.get( newUpgradeStack ); + if( newUpgrade == null || !TurtleUpgrades.suitableForFamily( ((TurtleBrain) turtle).getFamily(), newUpgrade ) ) + { + return TurtleCommandResult.failure( "Not a valid upgrade" ); + } + } + else + { + newUpgradeStack = null; + newUpgrade = null; + } + + // Determine the upgrade to replace + ItemStack oldUpgradeStack; + ITurtleUpgrade oldUpgrade = turtle.getUpgrade( m_side ); + if( oldUpgrade != null ) + { + ItemStack craftingItem = oldUpgrade.getCraftingItem(); + oldUpgradeStack = !craftingItem.isEmpty() ? craftingItem.copy() : null; + } + else + { + oldUpgradeStack = null; + } + + TurtleActionEvent event = new TurtleActionEvent( turtle, TurtleAction.EQUIP ); + if( TurtleEvent.post( event ) ) + { + return TurtleCommandResult.failure( event.getFailureMessage() ); + } + + // Do the swapping: + if( newUpgradeStack != null ) + { + // Consume new upgrades item + InventoryUtil.takeItems( 1, storage, turtle.getSelectedSlot(), 1, turtle.getSelectedSlot() ); + } + if( oldUpgradeStack != null ) + { + // Store old upgrades item + ItemStack remainder = InventoryUtil.storeItems( oldUpgradeStack, storage, turtle.getSelectedSlot() ); + if( !remainder.isEmpty() ) + { + // If there's no room for the items, drop them + BlockPos position = turtle.getPosition(); + WorldUtil.dropItemStack( remainder, turtle.getWorld(), position, turtle.getDirection() ); + } + } + turtle.setUpgrade( m_side, newUpgrade ); + + // Animate + if( newUpgrade != null || oldUpgrade != null ) + { + turtle.playAnimation( TurtleAnimation.Wait ); + } + + return TurtleCommandResult.success(); + } +} diff --git a/remappedSrc/dan200/computercraft/shared/turtle/core/TurtleInspectCommand.java b/remappedSrc/dan200/computercraft/shared/turtle/core/TurtleInspectCommand.java new file mode 100644 index 000000000..d000e8b73 --- /dev/null +++ b/remappedSrc/dan200/computercraft/shared/turtle/core/TurtleInspectCommand.java @@ -0,0 +1,83 @@ +/* + * 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.shared.turtle.core; + +import com.google.common.collect.ImmutableMap; +import dan200.computercraft.api.turtle.ITurtleAccess; +import dan200.computercraft.api.turtle.ITurtleCommand; +import dan200.computercraft.api.turtle.TurtleCommandResult; +import dan200.computercraft.api.turtle.event.TurtleBlockEvent; +import dan200.computercraft.api.turtle.event.TurtleEvent; +import net.minecraft.block.Block; +import net.minecraft.block.BlockState; +import net.minecraft.state.property.Property; +import net.minecraft.util.math.BlockPos; +import net.minecraft.util.math.Direction; +import net.minecraft.util.registry.Registry; +import net.minecraft.world.World; + +import javax.annotation.Nonnull; +import java.util.HashMap; +import java.util.Map; + +public class TurtleInspectCommand implements ITurtleCommand +{ + private final InteractDirection direction; + + public TurtleInspectCommand( InteractDirection direction ) + { + this.direction = direction; + } + + @Nonnull + @Override + public TurtleCommandResult execute( @Nonnull ITurtleAccess turtle ) + { + // Get world direction from direction + Direction direction = this.direction.toWorldDir( turtle ); + + // Check if thing in front is air or not + World world = turtle.getWorld(); + BlockPos oldPosition = turtle.getPosition(); + BlockPos newPosition = oldPosition.offset( direction ); + + BlockState state = world.getBlockState( newPosition ); + if( state.isAir() ) + { + return TurtleCommandResult.failure( "No block to inspect" ); + } + + Block block = state.getBlock(); + String name = Registry.BLOCK.getId( block ).toString(); + + Map table = new HashMap<>(); + table.put( "name", name ); + + Map stateTable = new HashMap<>(); + for( ImmutableMap.Entry, ? extends Comparable> entry : state.getEntries().entrySet() ) + { + Property property = entry.getKey(); + stateTable.put( property.getName(), getPropertyValue( property, entry.getValue() ) ); + } + table.put( "state", stateTable ); + + // Fire the event, exiting if it is cancelled + TurtlePlayer turtlePlayer = TurtlePlaceCommand.createPlayer( turtle, oldPosition, direction ); + TurtleBlockEvent.Inspect event = new TurtleBlockEvent.Inspect( turtle, turtlePlayer, world, newPosition, state, table ); + if( TurtleEvent.post( event ) ) return TurtleCommandResult.failure( event.getFailureMessage() ); + + return TurtleCommandResult.success( new Object[] { table } ); + + } + + @SuppressWarnings( { "unchecked", "rawtypes" } ) + private static Object getPropertyValue( Property property, Comparable value ) + { + if( value instanceof String || value instanceof Number || value instanceof Boolean ) return value; + return property.name( value ); + } +} diff --git a/remappedSrc/dan200/computercraft/shared/turtle/core/TurtleMoveCommand.java b/remappedSrc/dan200/computercraft/shared/turtle/core/TurtleMoveCommand.java new file mode 100644 index 000000000..690b224af --- /dev/null +++ b/remappedSrc/dan200/computercraft/shared/turtle/core/TurtleMoveCommand.java @@ -0,0 +1,173 @@ +/* + * 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.shared.turtle.core; + +import dan200.computercraft.ComputerCraft; +import dan200.computercraft.api.turtle.ITurtleAccess; +import dan200.computercraft.api.turtle.ITurtleCommand; +import dan200.computercraft.api.turtle.TurtleAnimation; +import dan200.computercraft.api.turtle.TurtleCommandResult; +import dan200.computercraft.api.turtle.event.TurtleBlockEvent; +import dan200.computercraft.api.turtle.event.TurtleEvent; +import dan200.computercraft.shared.TurtlePermissions; +import dan200.computercraft.shared.util.WorldUtil; +import net.minecraft.block.Block; +import net.minecraft.block.BlockState; +import net.minecraft.entity.Entity; +import net.minecraft.predicate.entity.EntityPredicates; +import net.minecraft.util.math.BlockPos; +import net.minecraft.util.math.Box; +import net.minecraft.util.math.Direction; +import net.minecraft.util.shape.VoxelShape; +import net.minecraft.world.World; + +import javax.annotation.Nonnull; +import java.util.List; + +public class TurtleMoveCommand implements ITurtleCommand +{ + private final MoveDirection m_direction; + + public TurtleMoveCommand( MoveDirection direction ) + { + m_direction = direction; + } + + @Nonnull + @Override + public TurtleCommandResult execute( @Nonnull ITurtleAccess turtle ) + { + // Get world direction from direction + Direction direction = m_direction.toWorldDir( turtle ); + + // Check if we can move + World oldWorld = turtle.getWorld(); + BlockPos oldPosition = turtle.getPosition(); + BlockPos newPosition = oldPosition.offset( direction ); + + TurtlePlayer turtlePlayer = TurtlePlaceCommand.createPlayer( turtle, oldPosition, direction ); + TurtleCommandResult canEnterResult = canEnter( turtlePlayer, oldWorld, newPosition ); + if( !canEnterResult.isSuccess() ) + { + return canEnterResult; + } + + // Check existing block is air or replaceable + BlockState state = oldWorld.getBlockState( newPosition ); + Block block = state.getBlock(); + if( block != null && + !oldWorld.isAir( newPosition ) && + !WorldUtil.isLiquidBlock( oldWorld, newPosition ) && + !state.getMaterial().isReplaceable() ) + { + return TurtleCommandResult.failure( "Movement obstructed" ); + } + + // Check there isn't anything in the way + Box aabb = getBox( state.getCollisionShape( oldWorld, oldPosition ) ); + aabb = aabb.offset( + newPosition.getX(), + newPosition.getY(), + newPosition.getZ() + ); + if( !oldWorld.doesNotCollide( aabb ) ) + { + if( !ComputerCraft.turtlesCanPush || m_direction == MoveDirection.Up || m_direction == MoveDirection.Down ) + { + return TurtleCommandResult.failure( "Movement obstructed" ); + } + + // Check there is space for all the pushable entities to be pushed + List list = oldWorld.getEntities( (Entity) null, aabb, EntityPredicates.VALID_ENTITY ); + for( Entity entity : list ) + { + Box entityBB = entity.getBoundingBox(); + if( entityBB == null ) continue; + + Box pushedBB = entityBB.offset( + direction.getOffsetX(), + direction.getOffsetY(), + direction.getOffsetZ() + ); + if( oldWorld.doesNotCollide( pushedBB ) ) + { + return TurtleCommandResult.failure( "Movement obstructed" ); + } + } + } + + TurtleBlockEvent.Move moveEvent = new TurtleBlockEvent.Move( turtle, turtlePlayer, oldWorld, newPosition ); + if( TurtleEvent.post( moveEvent ) ) + { + return TurtleCommandResult.failure( moveEvent.getFailureMessage() ); + } + + // Check fuel level + if( turtle.isFuelNeeded() && turtle.getFuelLevel() < 1 ) + { + return TurtleCommandResult.failure( "Out of fuel" ); + } + + // Move + if( !turtle.teleportTo( oldWorld, newPosition ) ) + { + return TurtleCommandResult.failure( "Movement failed" ); + } + + // Consume fuel + turtle.consumeFuel( 1 ); + + // Animate + switch( m_direction ) + { + case Forward: + default: + turtle.playAnimation( TurtleAnimation.MoveForward ); + break; + case Back: + turtle.playAnimation( TurtleAnimation.MoveBack ); + break; + case Up: + turtle.playAnimation( TurtleAnimation.MoveUp ); + break; + case Down: + turtle.playAnimation( TurtleAnimation.MoveDown ); + break; + } + return TurtleCommandResult.success(); + } + + private static TurtleCommandResult canEnter( TurtlePlayer turtlePlayer, World world, BlockPos position ) + { + if( World.isHeightInvalid( position ) ) + { + return TurtleCommandResult.failure( position.getY() < 0 ? "Too low to move" : "Too high to move" ); + } + if( !World.isValid( position ) ) return TurtleCommandResult.failure( "Cannot leave the world" ); + + // Check spawn protection + if( ComputerCraft.turtlesObeyBlockProtection && !TurtlePermissions.isBlockEnterable( world, position, turtlePlayer ) ) + { + return TurtleCommandResult.failure( "Cannot enter protected area" ); + } + + if( !world.isBlockLoaded( position ) ) return TurtleCommandResult.failure( "Cannot leave loaded world" ); + if( !world.getWorldBorder().contains( position ) ) + { + return TurtleCommandResult.failure( "Cannot pass the world border" ); + } + + return TurtleCommandResult.success(); + } + + private static Box getBox( VoxelShape shape ) + { + return shape.isEmpty() ? EMPTY_BOX : shape.getBoundingBox(); + } + + private static final Box EMPTY_BOX = new Box( 0, 0, 0, 0, 0, 0 ); +} diff --git a/remappedSrc/dan200/computercraft/shared/turtle/core/TurtlePlaceCommand.java b/remappedSrc/dan200/computercraft/shared/turtle/core/TurtlePlaceCommand.java new file mode 100644 index 000000000..78ef857f1 --- /dev/null +++ b/remappedSrc/dan200/computercraft/shared/turtle/core/TurtlePlaceCommand.java @@ -0,0 +1,398 @@ +/* + * 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.shared.turtle.core; + +import dan200.computercraft.ComputerCraft; +import dan200.computercraft.api.turtle.ITurtleAccess; +import dan200.computercraft.api.turtle.ITurtleCommand; +import dan200.computercraft.api.turtle.TurtleAnimation; +import dan200.computercraft.api.turtle.TurtleCommandResult; +import dan200.computercraft.api.turtle.event.TurtleBlockEvent; +import dan200.computercraft.api.turtle.event.TurtleEvent; +import dan200.computercraft.shared.TurtlePermissions; +import dan200.computercraft.shared.util.*; +import net.minecraft.block.entity.BlockEntity; +import net.minecraft.block.entity.SignBlockEntity; +import net.minecraft.entity.Entity; +import net.minecraft.entity.LivingEntity; +import net.minecraft.item.*; +import net.minecraft.text.LiteralText; +import net.minecraft.util.ActionResult; +import net.minecraft.util.Hand; +import net.minecraft.util.TypedActionResult; +import net.minecraft.util.hit.BlockHitResult; +import net.minecraft.util.math.BlockPos; +import net.minecraft.util.math.Direction; +import net.minecraft.util.math.Vec3d; +import net.minecraft.world.World; +import org.apache.commons.lang3.tuple.Pair; + +import javax.annotation.Nonnull; +import java.util.List; + +public class TurtlePlaceCommand implements ITurtleCommand +{ + private final InteractDirection m_direction; + private final Object[] m_extraArguments; + + public TurtlePlaceCommand( InteractDirection direction, Object[] arguments ) + { + m_direction = direction; + m_extraArguments = arguments; + } + + @Nonnull + @Override + public TurtleCommandResult execute( @Nonnull ITurtleAccess turtle ) + { + // Get thing to place + ItemStack stack = turtle.getInventory().getStack( turtle.getSelectedSlot() ); + if( stack.isEmpty() ) + { + return TurtleCommandResult.failure( "No items to place" ); + } + + // Remember old block + Direction direction = m_direction.toWorldDir( turtle ); + BlockPos coordinates = turtle.getPosition().offset( direction ); + + // Create a fake player, and orient it appropriately + BlockPos playerPosition = turtle.getPosition().offset( direction ); + TurtlePlayer turtlePlayer = createPlayer( turtle, playerPosition, direction ); + + TurtleBlockEvent.Place place = new TurtleBlockEvent.Place( turtle, turtlePlayer, turtle.getWorld(), coordinates, stack ); + if( TurtleEvent.post( place ) ) + { + return TurtleCommandResult.failure( place.getFailureMessage() ); + } + + // Do the deploying + String[] errorMessage = new String[1]; + ItemStack remainder = deploy( stack, turtle, turtlePlayer, direction, m_extraArguments, errorMessage ); + if( remainder != stack ) + { + // Put the remaining items back + turtle.getInventory().setStack( turtle.getSelectedSlot(), remainder ); + turtle.getInventory().markDirty(); + + // Animate and return success + turtle.playAnimation( TurtleAnimation.Wait ); + return TurtleCommandResult.success(); + } + else + { + if( errorMessage[0] != null ) + { + return TurtleCommandResult.failure( errorMessage[0] ); + } + else if( stack.getItem() instanceof BlockItem ) + { + return TurtleCommandResult.failure( "Cannot place block here" ); + } + else + { + return TurtleCommandResult.failure( "Cannot place item here" ); + } + } + } + + public static ItemStack deploy( @Nonnull ItemStack stack, ITurtleAccess turtle, Direction direction, Object[] extraArguments, String[] outErrorMessage ) + { + // Create a fake player, and orient it appropriately + BlockPos playerPosition = turtle.getPosition().offset( direction ); + TurtlePlayer turtlePlayer = createPlayer( turtle, playerPosition, direction ); + + return deploy( stack, turtle, turtlePlayer, direction, extraArguments, outErrorMessage ); + } + + public static ItemStack deploy( @Nonnull ItemStack stack, ITurtleAccess turtle, TurtlePlayer turtlePlayer, Direction direction, Object[] extraArguments, String[] outErrorMessage ) + { + // Deploy on an entity + ItemStack remainder = deployOnEntity( stack, turtle, turtlePlayer, direction, extraArguments, outErrorMessage ); + if( remainder != stack ) + { + return remainder; + } + + // Deploy on the block immediately in front + BlockPos position = turtle.getPosition(); + BlockPos newPosition = position.offset( direction ); + remainder = deployOnBlock( stack, turtle, turtlePlayer, newPosition, direction.getOpposite(), extraArguments, true, outErrorMessage ); + if( remainder != stack ) + { + return remainder; + } + + // Deploy on the block one block away + remainder = deployOnBlock( stack, turtle, turtlePlayer, newPosition.offset( direction ), direction.getOpposite(), extraArguments, false, outErrorMessage ); + if( remainder != stack ) + { + return remainder; + } + + if( direction.getAxis() != Direction.Axis.Y ) + { + // Deploy down on the block in front + remainder = deployOnBlock( stack, turtle, turtlePlayer, newPosition.down(), Direction.UP, extraArguments, false, outErrorMessage ); + if( remainder != stack ) + { + return remainder; + } + } + + // Deploy back onto the turtle + remainder = deployOnBlock( stack, turtle, turtlePlayer, position, direction, extraArguments, false, outErrorMessage ); + if( remainder != stack ) + { + return remainder; + } + + // If nothing worked, return the original stack unchanged + return stack; + } + + public static TurtlePlayer createPlayer( ITurtleAccess turtle, BlockPos position, Direction direction ) + { + TurtlePlayer turtlePlayer = TurtlePlayer.get( turtle ); + orientPlayer( turtle, turtlePlayer, position, direction ); + return turtlePlayer; + } + + private static void orientPlayer( ITurtleAccess turtle, TurtlePlayer turtlePlayer, BlockPos position, Direction direction ) + { + turtlePlayer.x = position.getX() + 0.5; + turtlePlayer.y = position.getY() + 0.5; + turtlePlayer.z = position.getZ() + 0.5; + + // Stop intersection with the turtle itself + if( turtle.getPosition().equals( position ) ) + { + turtlePlayer.x += 0.48 * direction.getOffsetX(); + turtlePlayer.y += 0.48 * direction.getOffsetY(); + turtlePlayer.z += 0.48 * direction.getOffsetZ(); + } + + if( direction.getAxis() != Direction.Axis.Y ) + { + turtlePlayer.yaw = direction.asRotation(); + turtlePlayer.pitch = 0.0f; + } + else + { + turtlePlayer.yaw = turtle.getDirection().asRotation(); + turtlePlayer.pitch = DirectionUtil.toPitchAngle( direction ); + } + + turtlePlayer.prevX = turtlePlayer.x; + turtlePlayer.prevY = turtlePlayer.y; + turtlePlayer.prevZ = turtlePlayer.z; + turtlePlayer.prevPitch = turtlePlayer.pitch; + turtlePlayer.prevYaw = turtlePlayer.yaw; + + turtlePlayer.headYaw = turtlePlayer.yaw; + turtlePlayer.prevHeadYaw = turtlePlayer.yaw; + } + + @Nonnull + private static ItemStack deployOnEntity( @Nonnull ItemStack stack, final ITurtleAccess turtle, TurtlePlayer turtlePlayer, Direction direction, Object[] extraArguments, String[] outErrorMessage ) + { + // See if there is an entity present + final World world = turtle.getWorld(); + final BlockPos position = turtle.getPosition(); + Vec3d turtlePos = new Vec3d( turtlePlayer.x, turtlePlayer.y, turtlePlayer.z ); + Vec3d rayDir = turtlePlayer.getRotationVec( 1.0f ); + Pair hit = WorldUtil.rayTraceEntities( world, turtlePos, rayDir, 1.5 ); + if( hit == null ) + { + return stack; + } + + // Load up the turtle's inventory + ItemStack stackCopy = stack.copy(); + turtlePlayer.loadInventory( stackCopy ); + + // Start claiming entity drops + Entity hitEntity = hit.getKey(); + Vec3d hitPos = hit.getValue(); + DropConsumer.set( + hitEntity, + drop -> InventoryUtil.storeItems( drop, ItemStorage.wrap( turtle.getInventory() ), turtle.getSelectedSlot() ) + ); + + // Place on the entity + boolean placed = false; + ActionResult cancelResult = hitEntity.interactAt( turtlePlayer, hitPos, Hand.MAIN_HAND ); + if( cancelResult == ActionResult.SUCCESS ) + { + placed = true; + } + else + { + if( hitEntity.interact( turtlePlayer, Hand.MAIN_HAND ) ) + { + placed = true; + } + else if( hitEntity instanceof LivingEntity ) + { + placed = stackCopy.useOnEntity( turtlePlayer, (LivingEntity) hitEntity, Hand.MAIN_HAND ); + if( placed ) turtlePlayer.loadInventory( stackCopy ); + } + } + + // Stop claiming drops + List remainingDrops = DropConsumer.clear(); + for( ItemStack remaining : remainingDrops ) + { + WorldUtil.dropItemStack( remaining, world, position, turtle.getDirection().getOpposite() ); + } + + // Put everything we collected into the turtles inventory, then return + ItemStack remainder = turtlePlayer.unloadInventory( turtle ); + if( !placed && ItemStack.areEqual( stack, remainder ) ) + { + return stack; + } + else if( !remainder.isEmpty() ) + { + return remainder; + } + else + { + return ItemStack.EMPTY; + } + } + + @Nonnull + private static ItemStack deployOnBlock( @Nonnull ItemStack stack, ITurtleAccess turtle, TurtlePlayer turtlePlayer, BlockPos position, Direction side, Object[] extraArguments, boolean allowReplace, String[] outErrorMessage ) + { + // Re-orient the fake player + Direction playerDir = side.getOpposite(); + BlockPos playerPosition = position.offset( side ); + orientPlayer( turtle, turtlePlayer, playerPosition, playerDir ); + + World world = turtle.getWorld(); + ItemStack stackCopy = stack.copy(); + turtlePlayer.loadInventory( stackCopy ); + + // Calculate where the turtle would hit the block + float hitX = 0.5f + side.getOffsetX() * 0.5f; + float hitY = 0.5f + side.getOffsetY() * 0.5f; + float hitZ = 0.5f + side.getOffsetZ() * 0.5f; + if( Math.abs( hitY - 0.5f ) < 0.01f ) + { + hitY = 0.45f; + } + + // Check if there's something suitable to place onto + BlockHitResult hit = new BlockHitResult( new Vec3d( hitX, hitY, hitZ ), side, position, false ); + ItemUsageContext context = new ItemUsageContext( turtlePlayer, Hand.MAIN_HAND, hit ); + ItemPlacementContext placementContext = new ItemPlacementContext( context ); + + if( !World.isValid( position ) || world.isAir( position ) || + (context.getStack().getItem() instanceof BlockItem && WorldUtil.isLiquidBlock( world, position )) ) + { + return stack; + } + + boolean replaceable = world.getBlockState( position ).canReplace( placementContext ); + if( !allowReplace && replaceable ) return stack; + + if( ComputerCraft.turtlesObeyBlockProtection ) + { + // Check spawn protection + boolean editable = replaceable + ? TurtlePermissions.isBlockEditable( world, position, turtlePlayer ) + : TurtlePermissions.isBlockEditable( world, position.offset( side ), turtlePlayer ); + if( !editable ) + { + if( outErrorMessage != null ) outErrorMessage[0] = "Cannot place in protected area"; + return stack; + } + } + + // Load up the turtle's inventory + Item item = stack.getItem(); + + // Do the deploying (put everything in the players inventory) + boolean placed = false; + BlockEntity existingTile = turtle.getWorld().getBlockEntity( position ); + + if( placementContext.canPlace() ) + { + if( stackCopy.useOnBlock( context ) == ActionResult.SUCCESS ) + { + placed = true; + turtlePlayer.loadInventory( stackCopy ); + } + } + + if( !placed && (item instanceof BucketItem || item instanceof BoatItem || item instanceof LilyPadItem || item instanceof GlassBottleItem) ) + { + TypedActionResult result = stackCopy.use( turtle.getWorld(), turtlePlayer, Hand.MAIN_HAND ); + if( result.getResult() == ActionResult.SUCCESS && !ItemStack.areEqual( stack, result.getValue() ) ) + { + placed = true; + turtlePlayer.loadInventory( result.getValue() ); + } + } + + // Set text on signs + if( placed && item instanceof SignItem ) + { + if( extraArguments != null && extraArguments.length >= 1 && extraArguments[0] instanceof String ) + { + BlockEntity tile = world.getBlockEntity( position ); + if( tile == null || tile == existingTile ) + { + tile = world.getBlockEntity( position.offset( side ) ); + } + if( tile instanceof SignBlockEntity ) + { + SignBlockEntity signTile = (SignBlockEntity) tile; + String s = (String) extraArguments[0]; + String[] split = s.split( "\n" ); + int firstLine = split.length <= 2 ? 1 : 0; + for( int i = 0; i < signTile.text.length; i++ ) + { + if( i >= firstLine && i < firstLine + split.length ) + { + if( split[i - firstLine].length() > 15 ) + { + signTile.text[i] = new LiteralText( split[i - firstLine].substring( 0, 15 ) ); + } + else + { + signTile.text[i] = new LiteralText( split[i - firstLine] ); + } + } + else + { + signTile.text[i] = new LiteralText( "" ); + } + } + signTile.markDirty(); + world.updateListeners( tile.getPos(), tile.getCachedState(), tile.getCachedState(), 3 ); + } + } + } + + // Put everything we collected into the turtles inventory, then return + ItemStack remainder = turtlePlayer.unloadInventory( turtle ); + if( !placed && ItemStack.areEqual( stack, remainder ) ) + { + return stack; + } + else if( !remainder.isEmpty() ) + { + return remainder; + } + else + { + return ItemStack.EMPTY; + } + } +} diff --git a/remappedSrc/dan200/computercraft/shared/turtle/core/TurtlePlayer.java b/remappedSrc/dan200/computercraft/shared/turtle/core/TurtlePlayer.java new file mode 100644 index 000000000..14026adc9 --- /dev/null +++ b/remappedSrc/dan200/computercraft/shared/turtle/core/TurtlePlayer.java @@ -0,0 +1,123 @@ +/* + * 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.shared.turtle.core; + +import com.mojang.authlib.GameProfile; +import dan200.computercraft.api.turtle.ITurtleAccess; +import dan200.computercraft.api.turtle.event.FakePlayer; +import dan200.computercraft.shared.util.InventoryUtil; +import dan200.computercraft.shared.util.ItemStorage; +import dan200.computercraft.shared.util.WorldUtil; +import net.minecraft.item.ItemStack; +import net.minecraft.server.world.ServerWorld; +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; +import java.util.UUID; + +public final class TurtlePlayer extends FakePlayer +{ + private static final GameProfile DEFAULT_PROFILE = new GameProfile( + UUID.fromString( "0d0c4ca0-4ff1-11e4-916c-0800200c9a66" ), + "[ComputerCraft]" + ); + + private TurtlePlayer( World world ) + { + super( (ServerWorld) world, DEFAULT_PROFILE ); + } + + private TurtlePlayer( ITurtleAccess turtle ) + { + super( (ServerWorld) turtle.getWorld(), getProfile( turtle.getOwningPlayer() ) ); + setState( turtle ); + } + + private static GameProfile getProfile( @Nullable GameProfile profile ) + { + return profile != null && profile.isComplete() ? profile : DEFAULT_PROFILE; + } + + private void setState( ITurtleAccess turtle ) + { + BlockPos position = turtle.getPosition(); + x = position.getX() + 0.5; + y = position.getY() + 0.5; + z = position.getZ() + 0.5; + + yaw = turtle.getDirection().asRotation(); + pitch = 0.0f; + + inventory.clear(); + } + + public static TurtlePlayer get( ITurtleAccess access ) + { + if( !(access instanceof TurtleBrain) ) return new TurtlePlayer( access ); + + TurtleBrain brain = (TurtleBrain) access; + TurtlePlayer player = brain.m_cachedPlayer; + if( player == null || player.getGameProfile() != getProfile( access.getOwningPlayer() ) + || player.getEntityWorld() != access.getWorld() ) + { + player = brain.m_cachedPlayer = new TurtlePlayer( brain ); + } + else + { + player.setState( access ); + } + + return player; + } + + public void loadInventory( @Nonnull ItemStack currentStack ) + { + // Load up the fake inventory + inventory.selectedSlot = 0; + inventory.setStack( 0, currentStack ); + } + + public ItemStack unloadInventory( ITurtleAccess turtle ) + { + // Get the item we placed with + ItemStack results = inventory.getStack( 0 ); + inventory.setStack( 0, ItemStack.EMPTY ); + + // Store (or drop) anything else we found + BlockPos dropPosition = turtle.getPosition(); + Direction dropDirection = turtle.getDirection().getOpposite(); + ItemStorage storage = ItemStorage.wrap( turtle.getInventory() ); + for( int i = 0; i < inventory.size(); ++i ) + { + ItemStack stack = inventory.getStack( i ); + if( !stack.isEmpty() ) + { + ItemStack remainder = InventoryUtil.storeItems( stack, storage, turtle.getSelectedSlot() ); + if( !remainder.isEmpty() ) + { + WorldUtil.dropItemStack( remainder, turtle.getWorld(), dropPosition, dropDirection ); + } + inventory.setStack( i, ItemStack.EMPTY ); + } + } + inventory.markDirty(); + return results; + } + + @Override + public Vec3d getCameraPosVec( float float_1 ) + { + y -= getStandingEyeHeight(); + Vec3d r = super.getCameraPosVec( float_1 ); + y += getStandingEyeHeight(); + return r; + } +} diff --git a/remappedSrc/dan200/computercraft/shared/turtle/core/TurtleRefuelCommand.java b/remappedSrc/dan200/computercraft/shared/turtle/core/TurtleRefuelCommand.java new file mode 100644 index 000000000..d6a293240 --- /dev/null +++ b/remappedSrc/dan200/computercraft/shared/turtle/core/TurtleRefuelCommand.java @@ -0,0 +1,48 @@ +/* + * 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.shared.turtle.core; + +import dan200.computercraft.api.turtle.ITurtleAccess; +import dan200.computercraft.api.turtle.ITurtleCommand; +import dan200.computercraft.api.turtle.TurtleAnimation; +import dan200.computercraft.api.turtle.TurtleCommandResult; +import dan200.computercraft.api.turtle.event.TurtleEvent; +import dan200.computercraft.api.turtle.event.TurtleRefuelEvent; +import net.minecraft.item.ItemStack; + +import javax.annotation.Nonnull; + +public class TurtleRefuelCommand implements ITurtleCommand +{ + private final int limit; + + public TurtleRefuelCommand( int limit ) + { + this.limit = limit; + } + + @Nonnull + @Override + public TurtleCommandResult execute( @Nonnull ITurtleAccess turtle ) + { + int slot = turtle.getSelectedSlot(); + ItemStack stack = turtle.getInventory().getStack( slot ); + if( stack.isEmpty() ) return TurtleCommandResult.failure( "No items to combust" ); + + TurtleRefuelEvent event = new TurtleRefuelEvent( turtle, stack ); + if( TurtleEvent.post( event ) ) return TurtleCommandResult.failure( event.getFailureMessage() ); + if( event.getHandler() == null ) return TurtleCommandResult.failure( "Items not combustible" ); + + if( limit != 0 ) + { + turtle.addFuel( event.getHandler().refuel( turtle, stack, slot, limit ) ); + turtle.playAnimation( TurtleAnimation.Wait ); + } + + return TurtleCommandResult.success(); + } +} diff --git a/remappedSrc/dan200/computercraft/shared/turtle/core/TurtleSuckCommand.java b/remappedSrc/dan200/computercraft/shared/turtle/core/TurtleSuckCommand.java new file mode 100644 index 000000000..1f32d85b3 --- /dev/null +++ b/remappedSrc/dan200/computercraft/shared/turtle/core/TurtleSuckCommand.java @@ -0,0 +1,172 @@ +/* + * 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.shared.turtle.core; + +import dan200.computercraft.api.turtle.ITurtleAccess; +import dan200.computercraft.api.turtle.ITurtleCommand; +import dan200.computercraft.api.turtle.TurtleAnimation; +import dan200.computercraft.api.turtle.TurtleCommandResult; +import dan200.computercraft.api.turtle.event.TurtleEvent; +import dan200.computercraft.api.turtle.event.TurtleInventoryEvent; +import dan200.computercraft.shared.util.InventoryUtil; +import dan200.computercraft.shared.util.ItemStorage; +import net.minecraft.entity.Entity; +import net.minecraft.entity.ItemEntity; +import net.minecraft.inventory.Inventory; +import net.minecraft.item.ItemStack; +import net.minecraft.predicate.entity.EntityPredicates; +import net.minecraft.util.math.BlockPos; +import net.minecraft.util.math.Box; +import net.minecraft.util.math.Direction; +import net.minecraft.world.World; + +import javax.annotation.Nonnull; +import java.util.List; + +public class TurtleSuckCommand implements ITurtleCommand +{ + private final InteractDirection m_direction; + private final int m_quantity; + + public TurtleSuckCommand( InteractDirection direction, int quantity ) + { + m_direction = direction; + m_quantity = quantity; + } + + @Nonnull + @Override + public TurtleCommandResult execute( @Nonnull ITurtleAccess turtle ) + { + // Sucking nothing is easy + if( m_quantity == 0 ) + { + turtle.playAnimation( TurtleAnimation.Wait ); + return TurtleCommandResult.success(); + } + + // Get world direction from direction + Direction direction = m_direction.toWorldDir( turtle ); + + // Get inventory for thing in front + World world = turtle.getWorld(); + BlockPos oldPosition = turtle.getPosition(); + BlockPos newPosition = oldPosition.offset( direction ); + Direction side = direction.getOpposite(); + + Inventory inventory = InventoryUtil.getInventory( world, newPosition, side ); + + // Fire the event, exiting if it is cancelled. + TurtlePlayer player = TurtlePlaceCommand.createPlayer( turtle, oldPosition, direction ); + TurtleInventoryEvent.Suck event = new TurtleInventoryEvent.Suck( turtle, player, world, newPosition, inventory ); + if( TurtleEvent.post( event ) ) + { + return TurtleCommandResult.failure( event.getFailureMessage() ); + } + + if( inventory != null ) + { + ItemStorage storage = ItemStorage.wrap( inventory, side ); + // Take from inventory of thing in front + ItemStack stack = InventoryUtil.takeItems( m_quantity, storage ); + if( stack.isEmpty() ) return TurtleCommandResult.failure( "No items to take" ); + + // Try to place into the turtle + ItemStack remainder = InventoryUtil.storeItems( stack, ItemStorage.wrap( turtle.getInventory() ), turtle.getSelectedSlot() ); + if( !remainder.isEmpty() ) + { + // Put the remainder back in the inventory + InventoryUtil.storeItems( remainder, storage ); + } + + // Return true if we consumed anything + if( remainder != stack ) + { + turtle.playAnimation( TurtleAnimation.Wait ); + return TurtleCommandResult.success(); + } + else + { + return TurtleCommandResult.failure( "No space for items" ); + } + } + else + { + // Suck up loose items off the ground + Box aabb = new Box( + newPosition.getX(), newPosition.getY(), newPosition.getZ(), + newPosition.getX() + 1.0, newPosition.getY() + 1.0, newPosition.getZ() + 1.0 + ); + List list = world.getEntities( (Entity) null, aabb, EntityPredicates.VALID_ENTITY ); + if( list.isEmpty() ) return TurtleCommandResult.failure( "No items to take" ); + + boolean foundItems = false; + boolean storedItems = false; + for( Entity entity : list ) + { + if( entity instanceof ItemEntity && entity.isAlive() ) + { + // Suck up the item + foundItems = true; + ItemEntity entityItem = (ItemEntity) entity; + ItemStack stack = entityItem.getStack().copy(); + ItemStack storeStack; + ItemStack leaveStack; + if( stack.getCount() > m_quantity ) + { + storeStack = stack.split( m_quantity ); + leaveStack = stack; + } + else + { + storeStack = stack; + leaveStack = ItemStack.EMPTY; + } + ItemStack remainder = InventoryUtil.storeItems( storeStack, ItemStorage.wrap( turtle.getInventory() ), turtle.getSelectedSlot() ); + if( remainder != storeStack ) + { + storedItems = true; + if( remainder.isEmpty() && leaveStack.isEmpty() ) + { + entityItem.remove(); + } + else if( remainder.isEmpty() ) + { + entityItem.setStack( leaveStack ); + } + else if( leaveStack.isEmpty() ) + { + entityItem.setStack( remainder ); + } + else + { + leaveStack.increment( remainder.getCount() ); + entityItem.setStack( leaveStack ); + } + break; + } + } + } + + if( foundItems ) + { + if( storedItems ) + { + // Play fx + world.syncGlobalEvent( 1000, oldPosition, 0 ); // BLOCK_DISPENSER_DISPENSE + turtle.playAnimation( TurtleAnimation.Wait ); + return TurtleCommandResult.success(); + } + else + { + return TurtleCommandResult.failure( "No space for items" ); + } + } + return TurtleCommandResult.failure( "No items to take" ); + } + } +} diff --git a/remappedSrc/dan200/computercraft/shared/turtle/core/TurtleToolCommand.java b/remappedSrc/dan200/computercraft/shared/turtle/core/TurtleToolCommand.java new file mode 100644 index 000000000..592233b06 --- /dev/null +++ b/remappedSrc/dan200/computercraft/shared/turtle/core/TurtleToolCommand.java @@ -0,0 +1,75 @@ +/* + * 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.shared.turtle.core; + +import dan200.computercraft.api.turtle.*; + +import javax.annotation.Nonnull; +import javax.annotation.Nullable; +import java.util.Locale; + +public class TurtleToolCommand implements ITurtleCommand +{ + private final TurtleVerb verb; + private final InteractDirection direction; + private final TurtleSide side; + + public TurtleToolCommand( TurtleVerb verb, InteractDirection direction, TurtleSide side ) + { + this.verb = verb; + this.direction = direction; + this.side = side; + } + + @Nonnull + @Override + public TurtleCommandResult execute( @Nonnull ITurtleAccess turtle ) + { + TurtleCommandResult firstFailure = null; + for( TurtleSide side : TurtleSide.values() ) + { + if( this.side != null && this.side != side ) continue; + + ITurtleUpgrade upgrade = turtle.getUpgrade( side ); + if( upgrade == null || !upgrade.getType().isTool() ) continue; + + TurtleCommandResult result = upgrade.useTool( turtle, side, verb, direction.toWorldDir( turtle ) ); + if( result.isSuccess() ) + { + switch( side ) + { + case Left: + turtle.playAnimation( TurtleAnimation.SwingLeftTool ); + break; + case Right: + turtle.playAnimation( TurtleAnimation.SwingRightTool ); + break; + default: + turtle.playAnimation( TurtleAnimation.Wait ); + break; + } + return result; + } + else if( firstFailure == null ) + { + firstFailure = result; + } + } + return firstFailure != null ? firstFailure + : TurtleCommandResult.failure( "No tool to " + verb.name().toLowerCase( Locale.ROOT ) + " with" ); + } + + public static TurtleToolCommand attack( InteractDirection direction, @Nullable TurtleSide side ) + { + return new TurtleToolCommand( TurtleVerb.Attack, direction, side ); + } + + public static TurtleToolCommand dig( InteractDirection direction, @Nullable TurtleSide side ) + { + return new TurtleToolCommand( TurtleVerb.Dig, direction, side ); + } +} diff --git a/remappedSrc/dan200/computercraft/shared/turtle/core/TurtleTransferToCommand.java b/remappedSrc/dan200/computercraft/shared/turtle/core/TurtleTransferToCommand.java new file mode 100644 index 000000000..6749f9ab4 --- /dev/null +++ b/remappedSrc/dan200/computercraft/shared/turtle/core/TurtleTransferToCommand.java @@ -0,0 +1,62 @@ +/* + * 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.shared.turtle.core; + +import dan200.computercraft.api.turtle.ITurtleAccess; +import dan200.computercraft.api.turtle.ITurtleCommand; +import dan200.computercraft.api.turtle.TurtleAnimation; +import dan200.computercraft.api.turtle.TurtleCommandResult; +import dan200.computercraft.shared.util.InventoryUtil; +import dan200.computercraft.shared.util.ItemStorage; +import net.minecraft.item.ItemStack; + +import javax.annotation.Nonnull; + +public class TurtleTransferToCommand implements ITurtleCommand +{ + private final int m_slot; + private final int m_quantity; + + public TurtleTransferToCommand( int slot, int limit ) + { + m_slot = slot; + m_quantity = limit; + } + + @Nonnull + @Override + public TurtleCommandResult execute( @Nonnull ITurtleAccess turtle ) + { + // Take stack + ItemStorage storage = ItemStorage.wrap( turtle.getInventory() ); + ItemStack stack = InventoryUtil.takeItems( m_quantity, storage, turtle.getSelectedSlot(), 1, turtle.getSelectedSlot() ); + if( stack.isEmpty() ) + { + turtle.playAnimation( TurtleAnimation.Wait ); + return TurtleCommandResult.success(); + } + + // Store stack + ItemStack remainder = InventoryUtil.storeItems( stack, storage, m_slot, 1, m_slot ); + if( !remainder.isEmpty() ) + { + // Put the remainder back + InventoryUtil.storeItems( remainder, storage, turtle.getSelectedSlot(), 1, turtle.getSelectedSlot() ); + } + + // Return true if we moved anything + if( remainder != stack ) + { + turtle.playAnimation( TurtleAnimation.Wait ); + return TurtleCommandResult.success(); + } + else + { + return TurtleCommandResult.failure( "No space for items" ); + } + } +} diff --git a/remappedSrc/dan200/computercraft/shared/turtle/core/TurtleTurnCommand.java b/remappedSrc/dan200/computercraft/shared/turtle/core/TurtleTurnCommand.java new file mode 100644 index 000000000..b7286d581 --- /dev/null +++ b/remappedSrc/dan200/computercraft/shared/turtle/core/TurtleTurnCommand.java @@ -0,0 +1,58 @@ +/* + * 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.shared.turtle.core; + +import dan200.computercraft.api.turtle.ITurtleAccess; +import dan200.computercraft.api.turtle.ITurtleCommand; +import dan200.computercraft.api.turtle.TurtleAnimation; +import dan200.computercraft.api.turtle.TurtleCommandResult; +import dan200.computercraft.api.turtle.event.TurtleAction; +import dan200.computercraft.api.turtle.event.TurtleActionEvent; +import dan200.computercraft.api.turtle.event.TurtleEvent; + +import javax.annotation.Nonnull; + +public class TurtleTurnCommand implements ITurtleCommand +{ + private final TurnDirection m_direction; + + public TurtleTurnCommand( TurnDirection direction ) + { + m_direction = direction; + } + + @Nonnull + @Override + public TurtleCommandResult execute( @Nonnull ITurtleAccess turtle ) + { + TurtleActionEvent event = new TurtleActionEvent( turtle, TurtleAction.TURN ); + if( TurtleEvent.post( event ) ) + { + return TurtleCommandResult.failure( event.getFailureMessage() ); + } + + switch( m_direction ) + { + case Left: + { + turtle.setDirection( turtle.getDirection().rotateYCounterclockwise() ); + turtle.playAnimation( TurtleAnimation.TurnLeft ); + return TurtleCommandResult.success(); + } + case Right: + { + turtle.setDirection( turtle.getDirection().rotateYClockwise() ); + turtle.playAnimation( TurtleAnimation.TurnRight ); + return TurtleCommandResult.success(); + } + default: + { + return TurtleCommandResult.failure( "Unknown direction" ); + } + } + } +} diff --git a/remappedSrc/dan200/computercraft/shared/turtle/inventory/ContainerTurtle.java b/remappedSrc/dan200/computercraft/shared/turtle/inventory/ContainerTurtle.java new file mode 100644 index 000000000..05f69daa8 --- /dev/null +++ b/remappedSrc/dan200/computercraft/shared/turtle/inventory/ContainerTurtle.java @@ -0,0 +1,178 @@ +/* + * 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.shared.turtle.inventory; + +import dan200.computercraft.api.turtle.ITurtleAccess; +import dan200.computercraft.shared.computer.core.IComputer; +import dan200.computercraft.shared.computer.core.IContainerComputer; +import dan200.computercraft.shared.computer.core.InputState; +import dan200.computercraft.shared.util.DefaultPropertyDelegate; +import net.minecraft.entity.player.PlayerEntity; +import net.minecraft.entity.player.PlayerInventory; +import net.minecraft.inventory.Inventory; +import net.minecraft.item.ItemStack; +import net.minecraft.screen.PropertyDelegate; +import net.minecraft.screen.ScreenHandler; +import net.minecraft.screen.slot.Slot; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class ContainerTurtle extends ScreenHandler implements IContainerComputer +{ + private static final int PROPERTY_SLOT = 0; + + public final int m_playerInvStartY; + public final int m_turtleInvStartX; + + private final Inventory inventory; + private final PropertyDelegate properties; + private IComputer computer; + private final InputState input = new InputState( this ); + private int m_selectedSlot; + + private ContainerTurtle( int id, PlayerInventory playerInventory, Inventory inventory, PropertyDelegate properties, int playerInvStartY, int turtleInvStartX ) + { + super( null, id ); + + m_playerInvStartY = playerInvStartY; + m_turtleInvStartX = turtleInvStartX; + + this.inventory = inventory; + this.properties = properties; + addProperties( properties ); + + // Turtle inventory + for( int y = 0; y < 4; y++ ) + { + for( int x = 0; x < 4; x++ ) + { + addSlot( new Slot( inventory, x + y * 4, turtleInvStartX + 1 + x * 18, playerInvStartY + 1 + y * 18 ) ); + } + } + + // Player inventory + for( int y = 0; y < 3; y++ ) + { + for( int x = 0; x < 9; x++ ) + { + addSlot( new Slot( playerInventory, x + y * 9 + 9, 8 + x * 18, playerInvStartY + 1 + y * 18 ) ); + } + } + + // Player hotbar + for( int x = 0; x < 9; x++ ) + { + addSlot( new Slot( playerInventory, x, 8 + x * 18, playerInvStartY + 3 * 18 + 5 ) ); + } + } + + public ContainerTurtle( int id, PlayerInventory playerInventory, Inventory inventory, PropertyDelegate properties ) + { + this( id, playerInventory, inventory, properties, 134, 175 ); + } + + public ContainerTurtle( int id, PlayerInventory playerInventory, ITurtleAccess turtle, IComputer computer ) + { + this( id, playerInventory, turtle.getInventory(), new DefaultPropertyDelegate() + { + @Override + public int get( int id ) + { + return id == PROPERTY_SLOT ? turtle.getSelectedSlot() : 0; + } + + @Override + public int size() + { + return 1; + } + } ); + this.computer = computer; + } + + public int getSelectedSlot() + { + return properties.get( PROPERTY_SLOT ); + } + + @Override + public boolean canUse( @Nonnull PlayerEntity player ) + { + return inventory.canPlayerUse( player ); + } + + @Nonnull + private ItemStack tryItemMerge( PlayerEntity player, int slotNum, int firstSlot, int lastSlot, boolean reverse ) + { + Slot slot = slots.get( slotNum ); + ItemStack originalStack = ItemStack.EMPTY; + if( slot != null && slot.hasStack() ) + { + ItemStack clickedStack = slot.getStack(); + originalStack = clickedStack.copy(); + if( !insertItem( clickedStack, firstSlot, lastSlot, reverse ) ) + { + return ItemStack.EMPTY; + } + + if( clickedStack.isEmpty() ) + { + slot.setStack( ItemStack.EMPTY ); + } + else + { + slot.markDirty(); + } + + if( clickedStack.getCount() != originalStack.getCount() ) + { + slot.onTakeItem( player, clickedStack ); + } + else + { + return ItemStack.EMPTY; + } + } + return originalStack; + } + + @Nonnull + @Override + public ItemStack transferSlot( PlayerEntity player, int slotNum ) + { + if( slotNum >= 0 && slotNum < 16 ) + { + return tryItemMerge( player, slotNum, 16, 52, true ); + } + else if( slotNum >= 16 ) + { + return tryItemMerge( player, slotNum, 0, 16, false ); + } + return ItemStack.EMPTY; + } + + @Nullable + @Override + public IComputer getComputer() + { + return computer; + } + + @Nonnull + @Override + public InputState getInput() + { + return input; + } + + @Override + public void close( PlayerEntity player ) + { + super.close( player ); + input.close(); + } +} diff --git a/remappedSrc/dan200/computercraft/shared/turtle/items/ITurtleItem.java b/remappedSrc/dan200/computercraft/shared/turtle/items/ITurtleItem.java new file mode 100644 index 000000000..ec63edb88 --- /dev/null +++ b/remappedSrc/dan200/computercraft/shared/turtle/items/ITurtleItem.java @@ -0,0 +1,28 @@ +/* + * 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.shared.turtle.items; + +import dan200.computercraft.api.turtle.ITurtleUpgrade; +import dan200.computercraft.api.turtle.TurtleSide; +import dan200.computercraft.shared.common.IColouredItem; +import dan200.computercraft.shared.computer.items.IComputerItem; +import net.minecraft.item.ItemStack; +import net.minecraft.util.Identifier; + +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public interface ITurtleItem extends IComputerItem, IColouredItem +{ + @Nullable + ITurtleUpgrade getUpgrade( @Nonnull ItemStack stack, @Nonnull TurtleSide side ); + + int getFuelLevel( @Nonnull ItemStack stack ); + + @Nullable + Identifier getOverlay( @Nonnull ItemStack stack ); +} diff --git a/remappedSrc/dan200/computercraft/shared/turtle/items/ItemTurtle.java b/remappedSrc/dan200/computercraft/shared/turtle/items/ItemTurtle.java new file mode 100644 index 000000000..fd2e25aab --- /dev/null +++ b/remappedSrc/dan200/computercraft/shared/turtle/items/ItemTurtle.java @@ -0,0 +1,140 @@ +/* + * 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.shared.turtle.items; + +import dan200.computercraft.api.turtle.ITurtleUpgrade; +import dan200.computercraft.api.turtle.TurtleSide; +import dan200.computercraft.shared.TurtleUpgrades; +import dan200.computercraft.shared.common.IColouredItem; +import dan200.computercraft.shared.computer.core.ComputerFamily; +import dan200.computercraft.shared.computer.items.ItemComputerBase; +import dan200.computercraft.shared.turtle.blocks.BlockTurtle; +import net.minecraft.item.ItemGroup; +import net.minecraft.item.ItemStack; +import net.minecraft.nbt.CompoundTag; +import net.minecraft.text.LiteralText; +import net.minecraft.text.Text; +import net.minecraft.text.TranslatableText; +import net.minecraft.util.Identifier; +import net.minecraft.util.collection.DefaultedList; +import javax.annotation.Nonnull; + +import static dan200.computercraft.shared.turtle.core.TurtleBrain.*; + +public class ItemTurtle extends ItemComputerBase implements ITurtleItem +{ + public ItemTurtle( BlockTurtle block, Settings settings ) + { + super( block, settings ); + } + + public ItemStack create( int id, String label, int colour, ITurtleUpgrade leftUpgrade, ITurtleUpgrade rightUpgrade, int fuelLevel, Identifier overlay ) + { + // Build the stack + ItemStack stack = new ItemStack( this ); + if( label != null ) stack.setCustomName( new LiteralText( label ) ); + if( id >= 0 ) stack.getOrCreateTag().putInt( NBT_ID, id ); + IColouredItem.setColourBasic( stack, colour ); + if( fuelLevel > 0 ) stack.getOrCreateTag().putInt( NBT_FUEL, fuelLevel ); + if( overlay != null ) stack.getOrCreateTag().putString( NBT_OVERLAY, overlay.toString() ); + + if( leftUpgrade != null ) + { + stack.getOrCreateTag().putString( NBT_LEFT_UPGRADE, leftUpgrade.getUpgradeID().toString() ); + } + + if( rightUpgrade != null ) + { + stack.getOrCreateTag().putString( NBT_RIGHT_UPGRADE, rightUpgrade.getUpgradeID().toString() ); + } + + return stack; + } + + @Override + public void appendStacks( @Nonnull ItemGroup group, @Nonnull DefaultedList list ) + { + if( !isIn( group ) ) return; + + ComputerFamily family = getFamily(); + + list.add( create( -1, null, -1, null, null, 0, null ) ); + for( ITurtleUpgrade upgrade : TurtleUpgrades.getVanillaUpgrades() ) + { + if( !TurtleUpgrades.suitableForFamily( family, upgrade ) ) continue; + + list.add( create( -1, null, -1, null, upgrade, 0, null ) ); + } + } + + @Nonnull + @Override + public Text getName( @Nonnull ItemStack stack ) + { + String baseString = getTranslationKey( stack ); + ITurtleUpgrade left = getUpgrade( stack, TurtleSide.Left ); + ITurtleUpgrade right = getUpgrade( stack, TurtleSide.Right ); + if( left != null && right != null ) + { + return new TranslatableText( baseString + ".upgraded_twice", + new TranslatableText( right.getUnlocalisedAdjective() ), + new TranslatableText( left.getUnlocalisedAdjective() ) + ); + } + else if( left != null ) + { + return new TranslatableText( baseString + ".upgraded", + new TranslatableText( left.getUnlocalisedAdjective() ) + ); + } + else if( right != null ) + { + return new TranslatableText( baseString + ".upgraded", + new TranslatableText( right.getUnlocalisedAdjective() ) + ); + } + else + { + return new TranslatableText( baseString ); + } + } + + @Override + public ItemStack withFamily( @Nonnull ItemStack stack, @Nonnull ComputerFamily family ) + { + return TurtleItemFactory.create( + getComputerID( stack ), getLabel( stack ), + getColour( stack ), family, + getUpgrade( stack, TurtleSide.Left ), getUpgrade( stack, TurtleSide.Right ), + getFuelLevel( stack ), getOverlay( stack ) + ); + } + + @Override + public ITurtleUpgrade getUpgrade( @Nonnull ItemStack stack, @Nonnull TurtleSide side ) + { + CompoundTag tag = stack.getTag(); + if( tag == null ) return null; + + String key = side == TurtleSide.Left ? NBT_LEFT_UPGRADE : NBT_RIGHT_UPGRADE; + return tag.contains( key ) ? TurtleUpgrades.get( tag.getString( key ) ) : null; + } + + @Override + public Identifier getOverlay( @Nonnull ItemStack stack ) + { + CompoundTag tag = stack.getTag(); + return tag != null && tag.contains( NBT_OVERLAY ) ? new Identifier( tag.getString( NBT_OVERLAY ) ) : null; + } + + @Override + public int getFuelLevel( @Nonnull ItemStack stack ) + { + CompoundTag tag = stack.getTag(); + return tag != null && tag.contains( NBT_FUEL ) ? tag.getInt( NBT_FUEL ) : 0; + } +} diff --git a/remappedSrc/dan200/computercraft/shared/turtle/items/TurtleItemFactory.java b/remappedSrc/dan200/computercraft/shared/turtle/items/TurtleItemFactory.java new file mode 100644 index 000000000..1c6ad9037 --- /dev/null +++ b/remappedSrc/dan200/computercraft/shared/turtle/items/TurtleItemFactory.java @@ -0,0 +1,53 @@ +/* + * 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.shared.turtle.items; + +import dan200.computercraft.ComputerCraft; +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.ITurtleTile; +import net.minecraft.item.ItemStack; +import net.minecraft.util.Identifier; + +import javax.annotation.Nonnull; + +public final class TurtleItemFactory +{ + private TurtleItemFactory() {} + + @Nonnull + public static ItemStack create( ITurtleTile turtle ) + { + ITurtleUpgrade leftUpgrade = turtle.getAccess().getUpgrade( TurtleSide.Left ); + ITurtleUpgrade rightUpgrade = turtle.getAccess().getUpgrade( TurtleSide.Right ); + + String label = turtle.getLabel(); + if( label == null ) + { + return create( -1, null, turtle.getColour(), turtle.getFamily(), leftUpgrade, rightUpgrade, 0, turtle.getOverlay() ); + } + + int id = turtle.getComputerID(); + int fuelLevel = turtle.getAccess().getFuelLevel(); + return create( id, label, turtle.getColour(), turtle.getFamily(), leftUpgrade, rightUpgrade, fuelLevel, turtle.getOverlay() ); + } + + @Nonnull + public static ItemStack create( int id, String label, int colour, ComputerFamily family, ITurtleUpgrade leftUpgrade, ITurtleUpgrade rightUpgrade, int fuelLevel, Identifier overlay ) + { + switch( family ) + { + case Normal: + return ComputerCraft.Items.turtleNormal.create( id, label, colour, leftUpgrade, rightUpgrade, fuelLevel, overlay ); + case Advanced: + return ComputerCraft.Items.turtleAdvanced.create( id, label, colour, leftUpgrade, rightUpgrade, fuelLevel, overlay ); + default: + return ItemStack.EMPTY; + } + } +} diff --git a/remappedSrc/dan200/computercraft/shared/turtle/recipes/TurtleRecipe.java b/remappedSrc/dan200/computercraft/shared/turtle/recipes/TurtleRecipe.java new file mode 100644 index 000000000..e4d368e50 --- /dev/null +++ b/remappedSrc/dan200/computercraft/shared/turtle/recipes/TurtleRecipe.java @@ -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.shared.turtle.recipes; + +import dan200.computercraft.shared.computer.core.ComputerFamily; +import dan200.computercraft.shared.computer.items.IComputerItem; +import dan200.computercraft.shared.computer.recipe.ComputerFamilyRecipe; +import dan200.computercraft.shared.turtle.items.TurtleItemFactory; +import net.minecraft.item.ItemStack; +import net.minecraft.recipe.Ingredient; +import net.minecraft.recipe.RecipeSerializer; +import net.minecraft.util.Identifier; +import net.minecraft.util.collection.DefaultedList; +import javax.annotation.Nonnull; + +public final class TurtleRecipe extends ComputerFamilyRecipe +{ + private TurtleRecipe( Identifier identifier, String group, int width, int height, DefaultedList ingredients, ItemStack result, ComputerFamily family ) + { + super( identifier, group, width, height, ingredients, result, family ); + } + + @Override + public RecipeSerializer getSerializer() + { + return SERIALIZER; + } + + @Nonnull + @Override + protected ItemStack convert( @Nonnull IComputerItem item, @Nonnull ItemStack stack ) + { + int computerID = item.getComputerID( stack ); + String label = item.getLabel( stack ); + + return TurtleItemFactory.create( computerID, label, -1, getFamily(), null, null, 0, null ); + } + + public static final RecipeSerializer SERIALIZER = new dan200.computercraft.shared.computer.recipe.ComputerFamilyRecipe.Serializer() + { + @Override + protected TurtleRecipe create( Identifier identifier, String group, int width, int height, DefaultedList ingredients, ItemStack result, ComputerFamily family ) + { + return new TurtleRecipe( identifier, group, width, height, ingredients, result, family ); + } + }; +} diff --git a/remappedSrc/dan200/computercraft/shared/turtle/recipes/TurtleUpgradeRecipe.java b/remappedSrc/dan200/computercraft/shared/turtle/recipes/TurtleUpgradeRecipe.java new file mode 100644 index 000000000..9b1fba6de --- /dev/null +++ b/remappedSrc/dan200/computercraft/shared/turtle/recipes/TurtleUpgradeRecipe.java @@ -0,0 +1,190 @@ +/* + * 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.shared.turtle.recipes; + +import dan200.computercraft.api.turtle.ITurtleUpgrade; +import dan200.computercraft.api.turtle.TurtleSide; +import dan200.computercraft.shared.TurtleUpgrades; +import dan200.computercraft.shared.computer.core.ComputerFamily; +import dan200.computercraft.shared.turtle.items.ITurtleItem; +import dan200.computercraft.shared.turtle.items.TurtleItemFactory; +import net.minecraft.inventory.CraftingInventory; +import net.minecraft.item.ItemStack; +import net.minecraft.recipe.RecipeSerializer; +import net.minecraft.recipe.SpecialCraftingRecipe; +import net.minecraft.recipe.SpecialRecipeSerializer; +import net.minecraft.util.Identifier; +import net.minecraft.world.World; + +import javax.annotation.Nonnull; + +public final class TurtleUpgradeRecipe extends SpecialCraftingRecipe +{ + private TurtleUpgradeRecipe( Identifier id ) + { + super( id ); + } + + @Override + public boolean fits( int x, int y ) + { + return x >= 3 && y >= 1; + } + + @Nonnull + @Override + public ItemStack getOutput() + { + return TurtleItemFactory.create( -1, null, -1, ComputerFamily.Normal, null, null, 0, null ); + } + + @Override + public boolean matches( @Nonnull CraftingInventory inventory, @Nonnull World world ) + { + return !craft( inventory ).isEmpty(); + } + + @Nonnull + @Override + public ItemStack craft( @Nonnull CraftingInventory inventory ) + { + // Scan the grid for a row containing a turtle and 1 or 2 items + ItemStack leftItem = ItemStack.EMPTY; + ItemStack turtle = ItemStack.EMPTY; + ItemStack rightItem = ItemStack.EMPTY; + + for( int y = 0; y < inventory.getHeight(); y++ ) + { + if( turtle.isEmpty() ) + { + // Search this row for potential turtles + boolean finishedRow = false; + for( int x = 0; x < inventory.getWidth(); x++ ) + { + ItemStack item = inventory.getStack( x + y * inventory.getWidth() ); + if( !item.isEmpty() ) + { + if( finishedRow ) + { + return ItemStack.EMPTY; + } + + if( item.getItem() instanceof ITurtleItem ) + { + // Item is a turtle + if( turtle.isEmpty() ) + { + turtle = item; + } + else + { + return ItemStack.EMPTY; + } + } + else + { + // Item is not a turtle + if( turtle.isEmpty() && leftItem.isEmpty() ) + { + leftItem = item; + } + else if( !turtle.isEmpty() && rightItem.isEmpty() ) + { + rightItem = item; + } + else + { + return ItemStack.EMPTY; + } + } + } + else + { + // Item is empty + if( !leftItem.isEmpty() || !turtle.isEmpty() ) + { + finishedRow = true; + } + } + } + + // If we found anything, check we found a turtle too + if( turtle.isEmpty() && (!leftItem.isEmpty() || !rightItem.isEmpty()) ) + { + return ItemStack.EMPTY; + } + } + else + { + // Turtle is already found, just check this row is empty + for( int x = 0; x < inventory.getWidth(); x++ ) + { + ItemStack item = inventory.getStack( x + y * inventory.getWidth() ); + if( !item.isEmpty() ) + { + return ItemStack.EMPTY; + } + } + } + } + + // See if we found a turtle + one or more items + if( turtle.isEmpty() || leftItem.isEmpty() && rightItem.isEmpty() ) + { + return ItemStack.EMPTY; + } + + // At this point we have a turtle + 1 or 2 items + // Get the turtle we already have + ITurtleItem itemTurtle = (ITurtleItem) turtle.getItem(); + ComputerFamily family = itemTurtle.getFamily(); + ITurtleUpgrade[] upgrades = new ITurtleUpgrade[] { + itemTurtle.getUpgrade( turtle, TurtleSide.Left ), + itemTurtle.getUpgrade( turtle, TurtleSide.Right ), + }; + + // Get the upgrades for the new items + ItemStack[] items = new ItemStack[] { rightItem, leftItem }; + for( int i = 0; i < 2; i++ ) + { + if( !items[i].isEmpty() ) + { + ITurtleUpgrade itemUpgrade = TurtleUpgrades.get( items[i] ); + if( itemUpgrade == null ) + { + return ItemStack.EMPTY; + } + if( upgrades[i] != null ) + { + return ItemStack.EMPTY; + } + if( !TurtleUpgrades.suitableForFamily( family, itemUpgrade ) ) + { + return ItemStack.EMPTY; + } + upgrades[i] = itemUpgrade; + } + } + + // Construct the new stack + int computerID = itemTurtle.getComputerID( turtle ); + String label = itemTurtle.getLabel( turtle ); + int fuelLevel = itemTurtle.getFuelLevel( turtle ); + int colour = itemTurtle.getColour( turtle ); + Identifier overlay = itemTurtle.getOverlay( turtle ); + return TurtleItemFactory.create( computerID, label, colour, family, upgrades[0], upgrades[1], fuelLevel, overlay ); + } + + @Nonnull + @Override + public RecipeSerializer getSerializer() + { + return SERIALIZER; + } + + public static final RecipeSerializer SERIALIZER = new SpecialRecipeSerializer<>( TurtleUpgradeRecipe::new ); +} diff --git a/remappedSrc/dan200/computercraft/shared/turtle/upgrades/CraftingTablePeripheral.java b/remappedSrc/dan200/computercraft/shared/turtle/upgrades/CraftingTablePeripheral.java new file mode 100644 index 000000000..13e12ba36 --- /dev/null +++ b/remappedSrc/dan200/computercraft/shared/turtle/upgrades/CraftingTablePeripheral.java @@ -0,0 +1,73 @@ +/* + * 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.shared.turtle.upgrades; + +import dan200.computercraft.api.lua.ILuaContext; +import dan200.computercraft.api.lua.LuaException; +import dan200.computercraft.api.peripheral.IComputerAccess; +import dan200.computercraft.api.peripheral.IPeripheral; +import dan200.computercraft.api.turtle.ITurtleAccess; +import dan200.computercraft.shared.turtle.core.TurtleCraftCommand; + +import javax.annotation.Nonnull; + +import static dan200.computercraft.core.apis.ArgumentHelper.optInt; + +public class CraftingTablePeripheral implements IPeripheral +{ + private final ITurtleAccess turtle; + + public CraftingTablePeripheral( ITurtleAccess turtle ) + { + this.turtle = turtle; + } + + @Nonnull + @Override + public String getType() + { + return "workbench"; + } + + @Nonnull + @Override + public String[] getMethodNames() + { + return new String[] { + "craft", + }; + } + + private static int parseCount( Object[] arguments ) throws LuaException + { + int count = optInt( arguments, 0, 64 ); + if( count < 0 || count > 64 ) throw new LuaException( "Crafting count " + count + " out of range" ); + return count; + } + + @Override + public Object[] callMethod( @Nonnull IComputerAccess computer, @Nonnull ILuaContext context, int method, @Nonnull Object[] arguments ) throws LuaException, InterruptedException + { + switch( method ) + { + case 0: + { + // craft + final int limit = parseCount( arguments ); + return turtle.executeCommand( context, new TurtleCraftCommand( limit ) ); + } + default: + return null; + } + } + + @Override + public boolean equals( IPeripheral other ) + { + return this == other || other instanceof CraftingTablePeripheral; + } +} diff --git a/remappedSrc/dan200/computercraft/shared/turtle/upgrades/TurtleAxe.java b/remappedSrc/dan200/computercraft/shared/turtle/upgrades/TurtleAxe.java new file mode 100644 index 000000000..67f8670b1 --- /dev/null +++ b/remappedSrc/dan200/computercraft/shared/turtle/upgrades/TurtleAxe.java @@ -0,0 +1,29 @@ +/* + * 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.shared.turtle.upgrades; + +import net.minecraft.item.Item; +import net.minecraft.util.Identifier; + +public class TurtleAxe extends TurtleTool +{ + public TurtleAxe( Identifier id, String adjective, Item item ) + { + super( id, adjective, item ); + } + + public TurtleAxe( Identifier id, Item item ) + { + super( id, item ); + } + + @Override + protected float getDamageMultiplier() + { + return 6.0f; + } +} diff --git a/remappedSrc/dan200/computercraft/shared/turtle/upgrades/TurtleCraftingTable.java b/remappedSrc/dan200/computercraft/shared/turtle/upgrades/TurtleCraftingTable.java new file mode 100644 index 000000000..5daa120bf --- /dev/null +++ b/remappedSrc/dan200/computercraft/shared/turtle/upgrades/TurtleCraftingTable.java @@ -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.shared.turtle.upgrades; + +import dan200.computercraft.api.AbstractTurtleUpgrade; +import dan200.computercraft.api.peripheral.IPeripheral; +import dan200.computercraft.api.turtle.ITurtleAccess; +import dan200.computercraft.api.turtle.TurtleSide; +import dan200.computercraft.api.turtle.TurtleUpgradeType; +import net.fabricmc.api.EnvType; +import net.fabricmc.api.Environment; +import net.minecraft.block.Blocks; +import net.minecraft.client.MinecraftClient; +import net.minecraft.client.render.model.BakedModel; +import net.minecraft.client.render.model.BakedModelManager; +import net.minecraft.client.util.ModelIdentifier; +import net.minecraft.util.Identifier; +import org.apache.commons.lang3.tuple.Pair; + +import javax.annotation.Nonnull; +import javax.vecmath.Matrix4f; + +public class TurtleCraftingTable extends AbstractTurtleUpgrade +{ + @Environment( EnvType.CLIENT ) + private ModelIdentifier m_leftModel; + + @Environment( EnvType.CLIENT ) + private ModelIdentifier m_rightModel; + + public TurtleCraftingTable( Identifier id ) + { + super( id, TurtleUpgradeType.Peripheral, Blocks.CRAFTING_TABLE ); + } + + @Override + public IPeripheral createPeripheral( @Nonnull ITurtleAccess turtle, @Nonnull TurtleSide side ) + { + return new CraftingTablePeripheral( turtle ); + } + + @Environment( EnvType.CLIENT ) + private void loadModelLocations() + { + if( m_leftModel == null ) + { + m_leftModel = new ModelIdentifier( "computercraft:turtle_crafting_table_left", "inventory" ); + m_rightModel = new ModelIdentifier( "computercraft:turtle_crafting_table_right", "inventory" ); + } + } + + @Nonnull + @Override + @Environment( EnvType.CLIENT ) + public Pair getModel( ITurtleAccess turtle, @Nonnull TurtleSide side ) + { + loadModelLocations(); + + Matrix4f transform = null; + BakedModelManager modelManager = MinecraftClient.getInstance().getItemRenderer().getModels().getModelManager(); + if( side == TurtleSide.Left ) + { + return Pair.of( modelManager.getModel( m_leftModel ), transform ); + } + else + { + return Pair.of( modelManager.getModel( m_rightModel ), transform ); + } + } +} diff --git a/remappedSrc/dan200/computercraft/shared/turtle/upgrades/TurtleHoe.java b/remappedSrc/dan200/computercraft/shared/turtle/upgrades/TurtleHoe.java new file mode 100644 index 000000000..63165fe46 --- /dev/null +++ b/remappedSrc/dan200/computercraft/shared/turtle/upgrades/TurtleHoe.java @@ -0,0 +1,67 @@ +/* + * 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.shared.turtle.upgrades; + +import dan200.computercraft.api.turtle.ITurtleAccess; +import dan200.computercraft.api.turtle.TurtleCommandResult; +import dan200.computercraft.api.turtle.TurtleSide; +import dan200.computercraft.api.turtle.TurtleVerb; +import dan200.computercraft.shared.turtle.core.TurtlePlaceCommand; +import dan200.computercraft.shared.turtle.core.TurtlePlayer; +import net.minecraft.block.BlockState; +import net.minecraft.block.Material; +import net.minecraft.item.Item; +import net.minecraft.item.ItemStack; +import net.minecraft.util.Identifier; +import net.minecraft.util.math.BlockPos; +import net.minecraft.util.math.Direction; +import net.minecraft.world.World; + +import javax.annotation.Nonnull; + +public class TurtleHoe extends TurtleTool +{ + public TurtleHoe( Identifier id, String adjective, Item item ) + { + super( id, adjective, item ); + } + + public TurtleHoe( Identifier id, Item item ) + { + super( id, item ); + } + + @Override + protected boolean canBreakBlock( BlockState state, World world, BlockPos pos, TurtlePlayer player ) + { + if( !super.canBreakBlock( state, world, pos, player ) ) return false; + + Material material = state.getMaterial(); + return material == Material.PLANT || + material == Material.CACTUS || + material == Material.GOURD || + material == Material.LEAVES || + material == Material.UNDERWATER_PLANT; + // material == Material.VINE; + } + + @Nonnull + @Override + public TurtleCommandResult useTool( @Nonnull ITurtleAccess turtle, @Nonnull TurtleSide side, @Nonnull TurtleVerb verb, @Nonnull Direction direction ) + { + if( verb == TurtleVerb.Dig ) + { + ItemStack hoe = m_item.copy(); + ItemStack remainder = TurtlePlaceCommand.deploy( hoe, turtle, direction, null, null ); + if( remainder != hoe ) + { + return TurtleCommandResult.success(); + } + } + return super.useTool( turtle, side, verb, direction ); + } +} diff --git a/remappedSrc/dan200/computercraft/shared/turtle/upgrades/TurtleInventoryCrafting.java b/remappedSrc/dan200/computercraft/shared/turtle/upgrades/TurtleInventoryCrafting.java new file mode 100644 index 000000000..925277533 --- /dev/null +++ b/remappedSrc/dan200/computercraft/shared/turtle/upgrades/TurtleInventoryCrafting.java @@ -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.shared.turtle.upgrades; + +import dan200.computercraft.api.turtle.ITurtleAccess; +import dan200.computercraft.shared.turtle.blocks.TileTurtle; +import dan200.computercraft.shared.turtle.core.TurtlePlayer; +import net.minecraft.entity.player.PlayerEntity; +import net.minecraft.inventory.CraftingInventory; +import net.minecraft.item.ItemStack; +import net.minecraft.recipe.CraftingRecipe; +import net.minecraft.recipe.RecipeType; +import net.minecraft.server.world.ServerWorld; +import net.minecraft.util.collection.DefaultedList; +import net.minecraft.world.World; + +import javax.annotation.Nonnull; +import javax.annotation.Nullable; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +public class TurtleInventoryCrafting extends CraftingInventory +{ + private ITurtleAccess m_turtle; + private int m_xStart; + private int m_yStart; + + @SuppressWarnings( "ConstantConditions" ) + public TurtleInventoryCrafting( ITurtleAccess turtle ) + { + // Passing null in here is evil, but we don't have a container present. We override most methods in order to + // avoid throwing any NPEs. + super( null, 0, 0 ); + m_turtle = turtle; + m_xStart = 0; + m_yStart = 0; + } + + @Nullable + private CraftingRecipe tryCrafting( int xStart, int yStart ) + { + m_xStart = xStart; + m_yStart = yStart; + + // Check the non-relevant parts of the inventory are empty + for( int x = 0; x < TileTurtle.INVENTORY_WIDTH; x++ ) + { + for( int y = 0; y < TileTurtle.INVENTORY_HEIGHT; y++ ) + { + if( x < m_xStart || x >= m_xStart + 3 || + y < m_yStart || y >= m_yStart + 3 ) + { + if( !m_turtle.getInventory().getStack( x + y * TileTurtle.INVENTORY_WIDTH ).isEmpty() ) + { + return null; + } + } + } + } + + // Check the actual crafting + return m_turtle.getWorld().getRecipeManager().getFirstMatch( RecipeType.CRAFTING, this, m_turtle.getWorld() ).orElse( null ); + } + + @Nullable + public List doCrafting( World world, int maxCount ) + { + if( world.isClient || !(world instanceof ServerWorld) ) return null; + + // Find out what we can craft + CraftingRecipe recipe = tryCrafting( 0, 0 ); + if( recipe == null ) recipe = tryCrafting( 0, 1 ); + if( recipe == null ) recipe = tryCrafting( 1, 0 ); + if( recipe == null ) recipe = tryCrafting( 1, 1 ); + if( recipe == null ) return null; + + // Special case: craft(0) just returns an empty list if crafting was possible + if( maxCount == 0 ) return Collections.emptyList(); + + TurtlePlayer player = TurtlePlayer.get( m_turtle ); + + ArrayList results = new ArrayList<>(); + for( int i = 0; i < maxCount && recipe.matches( this, world ); i++ ) + { + ItemStack result = recipe.craft( this ); + if( result.isEmpty() ) break; + results.add( result ); + + result.onCraft( world, player, result.getCount() ); + DefaultedList remainders = recipe.getRemainingStacks( this ); + + for( int slot = 0; slot < remainders.size(); slot++ ) + { + ItemStack existing = getStack( slot ); + ItemStack remainder = remainders.get( slot ); + + if( !existing.isEmpty() ) + { + removeStack( slot, 1 ); + existing = getStack( slot ); + } + + if( remainder.isEmpty() ) continue; + + // Either update the current stack or add it to the remainder list (to be inserted into the inventory + // afterwards). + if( existing.isEmpty() ) + { + setStack( slot, remainder ); + } + else if( existing.getItem() == remainder.getItem() && ItemStack.areTagsEqual( existing, remainder ) ) + { + remainder.increment( existing.getCount() ); + setStack( slot, remainder ); + } + else + { + results.add( remainder ); + } + } + } + + return results; + } + + @Override + public int getWidth() + { + return 3; + } + + @Override + public int getHeight() + { + return 3; + } + + private int modifyIndex( int index ) + { + int x = m_xStart + index % getWidth(); + int y = m_yStart + index / getHeight(); + return x >= 0 && x < TileTurtle.INVENTORY_WIDTH && y >= 0 && y < TileTurtle.INVENTORY_HEIGHT + ? x + y * TileTurtle.INVENTORY_WIDTH + : -1; + } + + // IInventory implementation + + @Override + public int size() + { + return getWidth() * getHeight(); + } + + @Nonnull + @Override + public ItemStack getStack( int i ) + { + i = modifyIndex( i ); + return m_turtle.getInventory().getStack( i ); + } + + @Nonnull + @Override + public ItemStack removeStack( int i ) + { + i = modifyIndex( i ); + return m_turtle.getInventory().removeStack( i ); + } + + @Nonnull + @Override + public ItemStack removeStack( int i, int size ) + { + i = modifyIndex( i ); + return m_turtle.getInventory().removeStack( i, size ); + } + + @Override + public void setStack( int i, @Nonnull ItemStack stack ) + { + i = modifyIndex( i ); + m_turtle.getInventory().setStack( i, stack ); + } + + @Override + public int getMaxCountPerStack() + { + return m_turtle.getInventory().getMaxCountPerStack(); + } + + @Override + public void markDirty() + { + m_turtle.getInventory().markDirty(); + } + + @Override + public boolean canPlayerUse( PlayerEntity player ) + { + return true; + } + + @Override + public boolean isValid( int i, @Nonnull ItemStack stack ) + { + i = modifyIndex( i ); + return m_turtle.getInventory().isValid( i, stack ); + } + + @Override + public void clear() + { + for( int i = 0; i < size(); i++ ) + { + int j = modifyIndex( i ); + m_turtle.getInventory().setStack( j, ItemStack.EMPTY ); + } + } +} diff --git a/remappedSrc/dan200/computercraft/shared/turtle/upgrades/TurtleModem.java b/remappedSrc/dan200/computercraft/shared/turtle/upgrades/TurtleModem.java new file mode 100644 index 000000000..8301a3cc2 --- /dev/null +++ b/remappedSrc/dan200/computercraft/shared/turtle/upgrades/TurtleModem.java @@ -0,0 +1,183 @@ +/* + * 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.shared.turtle.upgrades; + +import dan200.computercraft.ComputerCraft; +import dan200.computercraft.api.AbstractTurtleUpgrade; +import dan200.computercraft.api.peripheral.IPeripheral; +import dan200.computercraft.api.turtle.*; +import dan200.computercraft.shared.peripheral.modem.ModemState; +import dan200.computercraft.shared.peripheral.modem.wireless.WirelessModemPeripheral; +import net.fabricmc.api.EnvType; +import net.fabricmc.api.Environment; +import net.minecraft.client.MinecraftClient; +import net.minecraft.client.render.model.BakedModel; +import net.minecraft.client.render.model.BakedModelManager; +import net.minecraft.client.util.ModelIdentifier; +import net.minecraft.nbt.CompoundTag; +import net.minecraft.util.Identifier; +import net.minecraft.util.math.BlockPos; +import net.minecraft.util.math.Direction; +import net.minecraft.util.math.Vec3d; +import net.minecraft.world.World; +import org.apache.commons.lang3.tuple.Pair; + +import javax.annotation.Nonnull; +import javax.vecmath.Matrix4f; + +public class TurtleModem extends AbstractTurtleUpgrade +{ + private static class Peripheral extends WirelessModemPeripheral + { + private final ITurtleAccess turtle; + + Peripheral( ITurtleAccess turtle, boolean advanced ) + { + super( new ModemState(), advanced ); + this.turtle = turtle; + } + + @Nonnull + @Override + public World getWorld() + { + return turtle.getWorld(); + } + + @Nonnull + @Override + public Vec3d getPosition() + { + BlockPos turtlePos = turtle.getPosition(); + return new Vec3d( + turtlePos.getX(), + turtlePos.getY(), + turtlePos.getZ() + ); + } + + @Override + public boolean equals( IPeripheral other ) + { + return this == other || (other instanceof Peripheral && ((Peripheral) other).turtle == turtle); + } + } + + private boolean advanced; + + @Environment( EnvType.CLIENT ) + private ModelIdentifier m_leftOffModel; + + @Environment( EnvType.CLIENT ) + private ModelIdentifier m_rightOffModel; + + @Environment( EnvType.CLIENT ) + private ModelIdentifier m_leftOnModel; + + @Environment( EnvType.CLIENT ) + private ModelIdentifier m_rightOnModel; + + public TurtleModem( boolean advanced, Identifier id ) + { + super( + id, TurtleUpgradeType.Peripheral, + advanced + ? ComputerCraft.Blocks.wirelessModemAdvanced + : ComputerCraft.Blocks.wirelessModemNormal + ); + this.advanced = advanced; + } + + @Override + public IPeripheral createPeripheral( @Nonnull ITurtleAccess turtle, @Nonnull TurtleSide side ) + { + return new Peripheral( turtle, advanced ); + } + + @Nonnull + @Override + public TurtleCommandResult useTool( @Nonnull ITurtleAccess turtle, @Nonnull TurtleSide side, @Nonnull TurtleVerb verb, @Nonnull Direction dir ) + { + return TurtleCommandResult.failure(); + } + + @Environment( EnvType.CLIENT ) + private void loadModelLocations() + { + if( m_leftOffModel == null ) + { + if( advanced ) + { + m_leftOffModel = new ModelIdentifier( "computercraft:turtle_modem_advanced_off_left", "inventory" ); + m_rightOffModel = new ModelIdentifier( "computercraft:turtle_modem_advanced_off_right", "inventory" ); + m_leftOnModel = new ModelIdentifier( "computercraft:turtle_modem_advanced_on_left", "inventory" ); + m_rightOnModel = new ModelIdentifier( "computercraft:turtle_modem_advanced_on_right", "inventory" ); + } + else + { + m_leftOffModel = new ModelIdentifier( "computercraft:turtle_modem_normal_off_left", "inventory" ); + m_rightOffModel = new ModelIdentifier( "computercraft:turtle_modem_normal_off_right", "inventory" ); + m_leftOnModel = new ModelIdentifier( "computercraft:turtle_modem_normal_on_left", "inventory" ); + m_rightOnModel = new ModelIdentifier( "computercraft:turtle_modem_normal_on_right", "inventory" ); + } + } + } + + @Nonnull + @Override + @Environment( EnvType.CLIENT ) + public Pair getModel( ITurtleAccess turtle, @Nonnull TurtleSide side ) + { + loadModelLocations(); + + boolean active = false; + if( turtle != null ) + { + CompoundTag turtleNBT = turtle.getUpgradeNBTData( side ); + if( turtleNBT.contains( "active" ) ) + { + active = turtleNBT.getBoolean( "active" ); + } + } + + Matrix4f transform = null; + BakedModelManager modelManager = MinecraftClient.getInstance().getItemRenderer().getModels().getModelManager(); + if( side == TurtleSide.Left ) + { + return Pair.of( + active ? modelManager.getModel( m_leftOnModel ) : modelManager.getModel( m_leftOffModel ), + transform + ); + } + else + { + return Pair.of( + active ? modelManager.getModel( m_rightOnModel ) : modelManager.getModel( m_rightOffModel ), + transform + ); + } + } + + @Override + public void update( @Nonnull ITurtleAccess turtle, @Nonnull TurtleSide side ) + { + // Advance the modem + if( !turtle.getWorld().isClient ) + { + IPeripheral peripheral = turtle.getPeripheral( side ); + if( peripheral instanceof Peripheral ) + { + ModemState state = ((Peripheral) peripheral).getModemState(); + if( state.pollChanged() ) + { + turtle.getUpgradeNBTData( side ).putBoolean( "active", state.isOpen() ); + turtle.updateUpgradeNBTData( side ); + } + } + } + } +} diff --git a/remappedSrc/dan200/computercraft/shared/turtle/upgrades/TurtleShovel.java b/remappedSrc/dan200/computercraft/shared/turtle/upgrades/TurtleShovel.java new file mode 100644 index 000000000..cd6681982 --- /dev/null +++ b/remappedSrc/dan200/computercraft/shared/turtle/upgrades/TurtleShovel.java @@ -0,0 +1,72 @@ +/* + * 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.shared.turtle.upgrades; + +import dan200.computercraft.api.turtle.ITurtleAccess; +import dan200.computercraft.api.turtle.TurtleCommandResult; +import dan200.computercraft.api.turtle.TurtleSide; +import dan200.computercraft.api.turtle.TurtleVerb; +import dan200.computercraft.shared.turtle.core.TurtlePlaceCommand; +import dan200.computercraft.shared.turtle.core.TurtlePlayer; +import net.minecraft.block.BlockState; +import net.minecraft.block.Material; +import net.minecraft.item.Item; +import net.minecraft.item.ItemStack; +import net.minecraft.util.Identifier; +import net.minecraft.util.math.BlockPos; +import net.minecraft.util.math.Direction; +import net.minecraft.world.World; + +import javax.annotation.Nonnull; + +public class TurtleShovel extends TurtleTool +{ + public TurtleShovel( Identifier id, String adjective, Item item ) + { + super( id, adjective, item ); + } + + public TurtleShovel( Identifier id, Item item ) + { + super( id, item ); + } + + @Override + protected boolean canBreakBlock( BlockState state, World world, BlockPos pos, TurtlePlayer player ) + { + if( !super.canBreakBlock( state, world, pos, player ) ) return false; + + Material material = state.getMaterial(); + return material == Material.SOIL || + material == Material.AGGREGATE || + material == Material.SNOW_LAYER || + material == Material.ORGANIC_PRODUCT || + material == Material.SNOW_BLOCK || + material == Material.REPLACEABLE_PLANT || + material == Material.PLANT || + material == Material.CACTUS || + material == Material.GOURD || + material == Material.LEAVES; + // material == Material.VINE; + } + + @Nonnull + @Override + public TurtleCommandResult useTool( @Nonnull ITurtleAccess turtle, @Nonnull TurtleSide side, @Nonnull TurtleVerb verb, @Nonnull Direction direction ) + { + if( verb == TurtleVerb.Dig ) + { + ItemStack shovel = m_item.copy(); + ItemStack remainder = TurtlePlaceCommand.deploy( shovel, turtle, direction, null, null ); + if( remainder != shovel ) + { + return TurtleCommandResult.success(); + } + } + return super.useTool( turtle, side, verb, direction ); + } +} diff --git a/remappedSrc/dan200/computercraft/shared/turtle/upgrades/TurtleSpeaker.java b/remappedSrc/dan200/computercraft/shared/turtle/upgrades/TurtleSpeaker.java new file mode 100644 index 000000000..cd29979c6 --- /dev/null +++ b/remappedSrc/dan200/computercraft/shared/turtle/upgrades/TurtleSpeaker.java @@ -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.shared.turtle.upgrades; + +import dan200.computercraft.ComputerCraft; +import dan200.computercraft.api.AbstractTurtleUpgrade; +import dan200.computercraft.api.peripheral.IPeripheral; +import dan200.computercraft.api.turtle.ITurtleAccess; +import dan200.computercraft.api.turtle.TurtleSide; +import dan200.computercraft.api.turtle.TurtleUpgradeType; +import dan200.computercraft.shared.peripheral.speaker.SpeakerPeripheral; +import net.fabricmc.api.EnvType; +import net.fabricmc.api.Environment; +import net.minecraft.client.MinecraftClient; +import net.minecraft.client.render.model.BakedModel; +import net.minecraft.client.render.model.BakedModelManager; +import net.minecraft.client.util.ModelIdentifier; +import net.minecraft.util.Identifier; +import net.minecraft.util.math.BlockPos; +import net.minecraft.util.math.Vec3d; +import net.minecraft.world.World; +import org.apache.commons.lang3.tuple.Pair; + +import javax.annotation.Nonnull; +import javax.vecmath.Matrix4f; + +public class TurtleSpeaker extends AbstractTurtleUpgrade +{ + private static class Peripheral extends SpeakerPeripheral + { + ITurtleAccess turtle; + + Peripheral( ITurtleAccess turtle ) + { + this.turtle = turtle; + } + + @Override + public World getWorld() + { + return turtle.getWorld(); + } + + @Override + public Vec3d getPosition() + { + BlockPos pos = turtle.getPosition(); + return new Vec3d( pos.getX() + 0.5, pos.getY() + 0.5, pos.getZ() + 0.5 ); + } + + @Override + public boolean equals( IPeripheral other ) + { + return this == other || (other instanceof Peripheral && turtle == ((Peripheral) other).turtle); + } + } + + @Environment( EnvType.CLIENT ) + private ModelIdentifier m_leftModel; + + @Environment( EnvType.CLIENT ) + private ModelIdentifier m_rightModel; + + public TurtleSpeaker( Identifier id ) + { + super( id, TurtleUpgradeType.Peripheral, ComputerCraft.Blocks.speaker ); + } + + @Override + public IPeripheral createPeripheral( @Nonnull ITurtleAccess turtle, @Nonnull TurtleSide side ) + { + return new TurtleSpeaker.Peripheral( turtle ); + } + + @Environment( EnvType.CLIENT ) + private void loadModelLocations() + { + if( m_leftModel == null ) + { + m_leftModel = new ModelIdentifier( "computercraft:turtle_speaker_upgrade_left", "inventory" ); + m_rightModel = new ModelIdentifier( "computercraft:turtle_speaker_upgrade_right", "inventory" ); + } + } + + @Nonnull + @Override + @Environment( EnvType.CLIENT ) + public Pair getModel( ITurtleAccess turtle, @Nonnull TurtleSide side ) + { + loadModelLocations(); + BakedModelManager modelManager = MinecraftClient.getInstance().getItemRenderer().getModels().getModelManager(); + + if( side == TurtleSide.Left ) + { + return Pair.of( modelManager.getModel( m_leftModel ), null ); + } + else + { + return Pair.of( modelManager.getModel( m_rightModel ), null ); + } + } + + @Override + public void update( @Nonnull ITurtleAccess turtle, @Nonnull TurtleSide turtleSide ) + { + IPeripheral turtlePeripheral = turtle.getPeripheral( turtleSide ); + if( turtlePeripheral instanceof Peripheral ) + { + Peripheral peripheral = (Peripheral) turtlePeripheral; + peripheral.update(); + } + } +} diff --git a/remappedSrc/dan200/computercraft/shared/turtle/upgrades/TurtleSword.java b/remappedSrc/dan200/computercraft/shared/turtle/upgrades/TurtleSword.java new file mode 100644 index 000000000..afe221d66 --- /dev/null +++ b/remappedSrc/dan200/computercraft/shared/turtle/upgrades/TurtleSword.java @@ -0,0 +1,47 @@ +/* + * 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.shared.turtle.upgrades; + +import dan200.computercraft.shared.turtle.core.TurtlePlayer; +import net.minecraft.block.BlockState; +import net.minecraft.block.Material; +import net.minecraft.item.Item; +import net.minecraft.util.Identifier; +import net.minecraft.util.math.BlockPos; +import net.minecraft.world.World; + +public class TurtleSword extends TurtleTool +{ + public TurtleSword( Identifier id, String adjective, Item item ) + { + super( id, adjective, item ); + } + + public TurtleSword( Identifier id, Item item ) + { + super( id, item ); + } + + @Override + protected boolean canBreakBlock( BlockState state, World world, BlockPos pos, TurtlePlayer player ) + { + if( !super.canBreakBlock( state, world, pos, player ) ) return false; + + Material material = state.getMaterial(); + return material == Material.PLANT || + material == Material.LEAVES || + // material == Material.VINE || + material == Material.WOOL || + material == Material.COBWEB; + } + + @Override + protected float getDamageMultiplier() + { + return 9.0f; + } +} diff --git a/remappedSrc/dan200/computercraft/shared/turtle/upgrades/TurtleTool.java b/remappedSrc/dan200/computercraft/shared/turtle/upgrades/TurtleTool.java new file mode 100644 index 000000000..0667f6909 --- /dev/null +++ b/remappedSrc/dan200/computercraft/shared/turtle/upgrades/TurtleTool.java @@ -0,0 +1,276 @@ +/* + * 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.shared.turtle.upgrades; + +import dan200.computercraft.ComputerCraft; +import dan200.computercraft.api.AbstractTurtleUpgrade; +import dan200.computercraft.api.turtle.*; +import dan200.computercraft.api.turtle.event.TurtleAttackEvent; +import dan200.computercraft.api.turtle.event.TurtleBlockEvent; +import dan200.computercraft.api.turtle.event.TurtleEvent; +import dan200.computercraft.shared.TurtlePermissions; +import dan200.computercraft.shared.turtle.core.TurtlePlaceCommand; +import dan200.computercraft.shared.turtle.core.TurtlePlayer; +import dan200.computercraft.shared.util.DropConsumer; +import dan200.computercraft.shared.util.InventoryUtil; +import dan200.computercraft.shared.util.ItemStorage; +import dan200.computercraft.shared.util.WorldUtil; +import net.fabricmc.api.EnvType; +import net.fabricmc.api.Environment; +import net.fabricmc.fabric.api.event.player.AttackEntityCallback; +import net.minecraft.block.Block; +import net.minecraft.block.BlockState; +import net.minecraft.block.Blocks; +import net.minecraft.block.entity.BlockEntity; +import net.minecraft.client.MinecraftClient; +import net.minecraft.client.render.model.BakedModel; +import net.minecraft.entity.Entity; +import net.minecraft.entity.attribute.EntityAttributes; +import net.minecraft.entity.damage.DamageSource; +import net.minecraft.entity.decoration.ArmorStandEntity; +import net.minecraft.item.Item; +import net.minecraft.item.ItemStack; +import net.minecraft.util.ActionResult; +import net.minecraft.util.Hand; +import net.minecraft.util.Identifier; +import net.minecraft.util.math.BlockPos; +import net.minecraft.util.math.Direction; +import net.minecraft.util.math.Vec3d; +import net.minecraft.world.World; +import org.apache.commons.lang3.tuple.Pair; + +import javax.annotation.Nonnull; +import javax.vecmath.Matrix4f; +import java.util.List; +import java.util.function.Function; + +public class TurtleTool extends AbstractTurtleUpgrade +{ + protected ItemStack m_item; + + public TurtleTool( Identifier id, String adjective, Item item ) + { + super( id, TurtleUpgradeType.Tool, adjective, item ); + m_item = new ItemStack( item ); + } + + public TurtleTool( Identifier id, Item item ) + { + super( id, TurtleUpgradeType.Tool, item ); + m_item = new ItemStack( item ); + } + + @Nonnull + @Override + @Environment( EnvType.CLIENT ) + public Pair getModel( ITurtleAccess turtle, @Nonnull TurtleSide side ) + { + float xOffset = side == TurtleSide.Left ? -0.40625f : 0.40625f; + Matrix4f transform = new Matrix4f( new float[] { + 0.0f, 0.0f, -1.0f, 1.0f + xOffset, + 1.0f, 0.0f, 0.0f, 0.0f, + 0.0f, -1.0f, 0.0f, 1.0f, + 0.0f, 0.0f, 0.0f, 1.0f + } ); + return Pair.of( + MinecraftClient.getInstance().getItemRenderer().getModels().getModel( m_item ), + transform + ); + } + + @Nonnull + @Override + public TurtleCommandResult useTool( @Nonnull ITurtleAccess turtle, @Nonnull TurtleSide side, @Nonnull TurtleVerb verb, @Nonnull Direction direction ) + { + switch( verb ) + { + case Attack: + return attack( turtle, direction, side ); + case Dig: + return dig( turtle, direction, side ); + default: + return TurtleCommandResult.failure( "Unsupported action" ); + } + } + + protected boolean canBreakBlock( BlockState state, World world, BlockPos pos, TurtlePlayer player ) + { + Block block = state.getBlock(); + return !state.isAir() + && block != Blocks.BEDROCK + && state.calcBlockBreakingDelta( player, world, pos ) > 0; + } + + protected float getDamageMultiplier() + { + return 3.0f; + } + + private TurtleCommandResult attack( final ITurtleAccess turtle, Direction direction, TurtleSide side ) + { + // Create a fake player, and orient it appropriately + final World world = turtle.getWorld(); + final BlockPos position = turtle.getPosition(); + final TurtlePlayer turtlePlayer = TurtlePlaceCommand.createPlayer( turtle, position, direction ); + + // See if there is an entity present + Vec3d turtlePos = turtlePlayer.getPosVector(); + Vec3d rayDir = turtlePlayer.getRotationVec( 1.0f ); + Pair hit = WorldUtil.rayTraceEntities( world, turtlePos, rayDir, 1.5 ); + if( hit != null ) + { + // Load up the turtle's inventory + ItemStack stackCopy = m_item.copy(); + turtlePlayer.loadInventory( stackCopy ); + + Entity hitEntity = hit.getKey(); + + // Fire several events to ensure we have permissions. + if( AttackEntityCallback.EVENT.invoker().interact( turtlePlayer, world, Hand.MAIN_HAND, hitEntity, null ) == ActionResult.FAIL + || !hitEntity.isAttackable() ) + { + return TurtleCommandResult.failure( "Nothing to attack here" ); + } + + TurtleAttackEvent attackEvent = new TurtleAttackEvent( turtle, turtlePlayer, hitEntity, this, side ); + if( TurtleEvent.post( attackEvent ) ) + { + return TurtleCommandResult.failure( attackEvent.getFailureMessage() ); + } + + // Start claiming entity drops + DropConsumer.set( hitEntity, turtleDropConsumer( turtle ) ); + + // Attack the entity + boolean attacked = false; + if( !hitEntity.handleAttack( turtlePlayer ) ) + { + float damage = (float) turtlePlayer.getAttributeInstance( EntityAttributes.ATTACK_DAMAGE ).getValue(); + damage *= getDamageMultiplier(); + if( damage > 0.0f ) + { + // TODO: Is this sufficient? I feel we need to do velocity updates here now. + DamageSource source = DamageSource.player( turtlePlayer ); + if( hitEntity instanceof ArmorStandEntity ) + { + // Special case for armor stands: attack twice to guarantee destroy + hitEntity.damage( source, damage ); + if( hitEntity.isAlive() ) hitEntity.damage( source, damage ); + attacked = true; + } + else + { + if( hitEntity.damage( source, damage ) ) + { + attacked = true; + } + } + } + } + + // Stop claiming drops + stopConsuming( turtle ); + + // Put everything we collected into the turtles inventory, then return + if( attacked ) + { + turtlePlayer.unloadInventory( turtle ); + return TurtleCommandResult.success(); + } + } + + return TurtleCommandResult.failure( "Nothing to attack here" ); + } + + private TurtleCommandResult dig( ITurtleAccess turtle, Direction direction, TurtleSide side ) + { + // Get ready to dig + World world = turtle.getWorld(); + BlockPos turtlePosition = turtle.getPosition(); + BlockPos blockPosition = turtlePosition.offset( direction ); + + if( world.isAir( blockPosition ) || WorldUtil.isLiquidBlock( world, blockPosition ) ) + { + return TurtleCommandResult.failure( "Nothing to dig here" ); + } + + BlockState state = world.getBlockState( blockPosition ); + + TurtlePlayer turtlePlayer = TurtlePlaceCommand.createPlayer( turtle, turtlePosition, direction ); + turtlePlayer.loadInventory( m_item.copy() ); + + if( ComputerCraft.turtlesObeyBlockProtection ) + { + // Check spawn protection + /* + if( MinecraftForge.EVENT_BUS.post( new BlockEvent.BreakEvent( world, blockPosition, state, turtlePlayer ) ) ) + { + return TurtleCommandResult.failure( "Cannot break protected block" ); + } + */ + + if( !TurtlePermissions.isBlockEditable( world, blockPosition, turtlePlayer ) ) + { + return TurtleCommandResult.failure( "Cannot break protected block" ); + } + } + + // Check if we can break the block + if( !canBreakBlock( state, world, blockPosition, turtlePlayer ) ) + { + return TurtleCommandResult.failure( "Unbreakable block detected" ); + } + + // Fire the dig event, checking whether it was cancelled. + TurtleBlockEvent.Dig digEvent = new TurtleBlockEvent.Dig( turtle, turtlePlayer, world, blockPosition, state, this, side ); + if( TurtleEvent.post( digEvent ) ) + { + return TurtleCommandResult.failure( digEvent.getFailureMessage() ); + } + + // Consume the items the block drops + DropConsumer.set( world, blockPosition, turtleDropConsumer( turtle ) ); + + BlockEntity tile = world.getBlockEntity( blockPosition ); + + // Much of this logic comes from PlayerInteractionManager#tryHarvestBlock, so it's a good idea + // to consult there before making any changes. + + // Play the destruction sound and particles + world.syncGlobalEvent( 2001, blockPosition, Block.getRawIdFromState( state ) ); + + // Destroy the block + state.getBlock().onBreak( world, blockPosition, state, turtlePlayer ); + if( world.removeBlock( blockPosition, false ) ) + { + state.getBlock().onBroken( world, blockPosition, state ); + if( turtlePlayer.isUsingEffectiveTool( state ) ) + { + state.getBlock().afterBreak( world, turtlePlayer, blockPosition, state, tile, m_item.copy() ); + } + } + + stopConsuming( turtle ); + + return TurtleCommandResult.success(); + + } + + private static Function turtleDropConsumer( ITurtleAccess turtle ) + { + return drop -> InventoryUtil.storeItems( drop, ItemStorage.wrap( turtle.getInventory() ), turtle.getSelectedSlot() ); + } + + private static void stopConsuming( ITurtleAccess turtle ) + { + List extra = DropConsumer.clear(); + for( ItemStack remainder : extra ) + { + WorldUtil.dropItemStack( remainder, turtle.getWorld(), turtle.getPosition(), turtle.getDirection().getOpposite() ); + } + } +} diff --git a/remappedSrc/dan200/computercraft/shared/util/Colour.java b/remappedSrc/dan200/computercraft/shared/util/Colour.java new file mode 100644 index 000000000..ac383a58f --- /dev/null +++ b/remappedSrc/dan200/computercraft/shared/util/Colour.java @@ -0,0 +1,92 @@ +/* + * 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.shared.util; + +public enum Colour +{ + Black( 0x111111 ), + Red( 0xcc4c4c ), + Green( 0x57A64E ), + Brown( 0x7f664c ), + Blue( 0x3366cc ), + Purple( 0xb266e5 ), + Cyan( 0x4c99b2 ), + LightGrey( 0x999999 ), + Grey( 0x4c4c4c ), + Pink( 0xf2b2cc ), + Lime( 0x7fcc19 ), + Yellow( 0xdede6c ), + LightBlue( 0x99b2f2 ), + Magenta( 0xe57fd8 ), + Orange( 0xf2b233 ), + White( 0xf0f0f0 ); + + public static final Colour[] VALUES = values(); + + public static Colour fromInt( int colour ) + { + return colour >= 0 && colour < 16 ? Colour.VALUES[colour] : null; + } + + public static Colour fromHex( int colour ) + { + for( Colour entry : VALUES ) + { + if( entry.getHex() == colour ) return entry; + } + + return null; + } + + private final int hex; + private final float[] rgb; + + Colour( int hex ) + { + this.hex = hex; + rgb = new float[] { + ((hex >> 16) & 0xFF) / 255.0f, + ((hex >> 8) & 0xFF) / 255.0f, + (hex & 0xFF) / 255.0f, + }; + } + + public Colour getNext() + { + return VALUES[(ordinal() + 1) % 16]; + } + + public Colour getPrevious() + { + return VALUES[(ordinal() + 15) % 16]; + } + + public int getHex() + { + return hex; + } + + public float[] getRGB() + { + return rgb; + } + + public float getR() + { + return rgb[0]; + } + + public float getG() + { + return rgb[1]; + } + + public float getB() + { + return rgb[2]; + } +} diff --git a/remappedSrc/dan200/computercraft/shared/util/ColourTracker.java b/remappedSrc/dan200/computercraft/shared/util/ColourTracker.java new file mode 100644 index 000000000..41152fb4c --- /dev/null +++ b/remappedSrc/dan200/computercraft/shared/util/ColourTracker.java @@ -0,0 +1,54 @@ +/* + * 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.shared.util; + +/** + * A reimplementation of the colour system in {@link net.minecraft.item.crafting.RecipesArmorDyes}, but + * bundled together as an object. + */ +public class ColourTracker +{ + private int total; + private int totalR; + private int totalG; + private int totalB; + private int count; + + public void addColour( int r, int g, int b ) + { + total += Math.max( r, Math.max( g, b ) ); + totalR += r; + totalG += g; + totalB += b; + count++; + } + + public void addColour( float r, float g, float b ) + { + addColour( (int) (r * 255), (int) (g * 255), (int) (b * 255) ); + } + + public boolean hasColour() + { + return count > 0; + } + + public int getColour() + { + int avgR = totalR / count; + int avgG = totalG / count; + int avgB = totalB / count; + + float avgTotal = (float) total / count; + float avgMax = Math.max( avgR, Math.max( avgG, avgB ) ); + avgR = (int) (avgR * avgTotal / avgMax); + avgG = (int) (avgG * avgTotal / avgMax); + avgB = (int) (avgB * avgTotal / avgMax); + + return (avgR << 16) | (avgG << 8) | avgB; + } +} diff --git a/remappedSrc/dan200/computercraft/shared/util/ColourUtils.java b/remappedSrc/dan200/computercraft/shared/util/ColourUtils.java new file mode 100644 index 000000000..b41f0d38f --- /dev/null +++ b/remappedSrc/dan200/computercraft/shared/util/ColourUtils.java @@ -0,0 +1,23 @@ +/* + * 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.shared.util; + +import net.minecraft.item.DyeItem; +import net.minecraft.item.Item; +import net.minecraft.item.ItemStack; +import net.minecraft.util.DyeColor; + +public final class ColourUtils +{ + private ColourUtils() {} + + public static DyeColor getStackColour( ItemStack stack ) + { + Item item = stack.getItem(); + return item instanceof DyeItem ? ((DyeItem) item).getColor() : null; + } +} diff --git a/remappedSrc/dan200/computercraft/shared/util/Config.java b/remappedSrc/dan200/computercraft/shared/util/Config.java new file mode 100644 index 000000000..f7f80d85c --- /dev/null +++ b/remappedSrc/dan200/computercraft/shared/util/Config.java @@ -0,0 +1,282 @@ +package dan200.computercraft.shared.util; + +import blue.endless.jankson.Comment; +import blue.endless.jankson.Jankson; +import blue.endless.jankson.JsonObject; +import blue.endless.jankson.impl.SyntaxError; +import com.google.common.base.CaseFormat; +import com.google.common.base.Converter; +import dan200.computercraft.ComputerCraft; +import dan200.computercraft.api.turtle.event.TurtleAction; +import dan200.computercraft.core.apis.AddressPredicate; +import dan200.computercraft.core.apis.http.websocket.Websocket; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.concurrent.TimeUnit; + +public class Config +{ + public static final transient int MODEM_MAX_RANGE = 100000; + + public General general = new General(); + + @Comment( "\nControls execution behaviour of computers. This is largely intended for fine-tuning " + + "servers, and generally shouldn't need to be touched" ) + public Execution execution = new Execution(); + + @Comment( "\nControls the HTTP API" ) + public Http http = new Http(); + + @Comment( "\nVarious options relating to peripherals." ) + public Peripheral peripheral = new Peripheral(); + + @Comment( "\nVarious options relating to turtles." ) + public Turtle turtle = new Turtle(); + + public static class General + { + @Comment( "\nThe disk space limit for computers and turtles, in bytes" ) + public int computer_space_limit = ComputerCraft.computerSpaceLimit; + + @Comment( "\nThe disk space limit for floppy disks, in bytes" ) + public int floppy_space_limit = ComputerCraft.floppySpaceLimit; + + @Comment( "\nSet how many files a computer can have open at the same time. Set to 0 for unlimited." ) + public int maximum_open_files = ComputerCraft.maximumFilesOpen; + + @Comment( "\nSet this to true to disable Lua 5.1 functions that will be removed in a future " + + "update. Useful for ensuring forward compatibility of your programs now." ) + public boolean disable_lua51_features = ComputerCraft.disable_lua51_features; + + @Comment( "\nA comma separated list of default system settings to set on new computers. Example: " + + "\"shell.autocomplete=false,lua.autocomplete=false,edit.autocomplete=false\" will disable all autocompletion" ) + public String default_computer_settings = ComputerCraft.default_computer_settings; + + @Comment( "\nEnable Lua's debug library. This is sandboxed to each computer, so is generally safe to be used by players." ) + public boolean debug_enabled = ComputerCraft.debug_enable; + + @Comment( "\nLog exceptions thrown by peripherals and other Lua objects.\n" + + "This makes it easier for mod authors to debug problems, but may result in log spam should people use buggy methods." ) + public boolean log_computer_errors = ComputerCraft.logPeripheralErrors; + } + + public static class Execution + { + @Comment( "\nSet the number of threads computers can run on. A higher number means more computers can " + + "run at once, but may induce lag.\n" + + "Please note that some mods may not work with a thread count higher than 1. Use with caution." ) + public int computer_threads = ComputerCraft.computer_threads; + + @Comment( "\nThe maximum time that can be spent executing tasks in a single tick, in milliseconds.\n" + + "Note, we will quite possibly go over this limit, as there's no way to tell how long a will take - this aims " + + "to be the upper bound of the average time." ) + public long max_main_global_time = TimeUnit.NANOSECONDS.toMillis( ComputerCraft.maxMainGlobalTime ); + + @Comment( "\nThe ideal maximum time a computer can execute for in a tick, in milliseconds.\n" + + "Note, we will quite possibly go over this limit, as there's no way to tell how long a will take - this aims " + + "to be the upper bound of the average time." ) + public long max_main_computer_time = TimeUnit.NANOSECONDS.toMillis( ComputerCraft.maxMainComputerTime ); + } + + public static class Http + { + @Comment( "\nEnable the \"http\" API on Computers (see \"http_whitelist\" and \"http_blacklist\" for " + + "more fine grained control than this)" ) + public boolean enabled = ComputerCraft.http_enable; + + @Comment( "\nEnable use of http websockets. This requires the \"http_enable\" option to also be true." ) + public boolean websocket_enabled = ComputerCraft.http_websocket_enable; + + @Comment( "\nA list of wildcards for domains or IP ranges that can be accessed through the " + + "\"http\" API on Computers.\n" + + "Set this to \"*\" to access to the entire internet. Example: \"*.pastebin.com\" will restrict access to " + + "just subdomains of pastebin.com.\n" + + "You can use domain names (\"pastebin.com\"), wilcards (\"*.pastebin.com\") or CIDR notation (\"127.0.0.0/8\")." ) + public String[] whitelist = ComputerCraft.DEFAULT_HTTP_WHITELIST.clone(); + + @Comment( "\nA list of wildcards for domains or IP ranges that cannot be accessed through the " + + "\"http\" API on Computers.\n" + + "If this is empty then all whitelisted domains will be accessible. Example: \"*.github.com\" will block " + + "access to all subdomains of github.com.\n" + + "You can use domain names (\"pastebin.com\"), wilcards (\"*.pastebin.com\") or CIDR notation (\"127.0.0.0/8\")." ) + public String[] blacklist = ComputerCraft.DEFAULT_HTTP_BLACKLIST.clone(); + + @Comment( "\nThe period of time (in milliseconds) to wait before a HTTP request times out. Set to 0 for unlimited." ) + public int timeout = ComputerCraft.httpTimeout; + + @Comment( "\nThe number of http requests a computer can make at one time. Additional requests " + + "will be queued, and sent when the running requests have finished. Set to 0 for unlimited." ) + public int max_requests = ComputerCraft.httpMaxRequests; + + @Comment( "\nThe maximum size (in bytes) that a computer can download in a single request. " + + "Note that responses may receive more data than allowed, but this data will not be returned to the client." ) + public long max_download = ComputerCraft.httpMaxDownload; + + @Comment( "\nThe maximum size (in bytes) that a computer can upload in a single request. This " + + "includes headers and POST text." ) + public long max_upload = ComputerCraft.httpMaxUpload; + + @Comment( "\nThe number of websockets a computer can have open at one time. Set to 0 for unlimited." ) + public int max_websockets = ComputerCraft.httpMaxWebsockets; + + @Comment( "\nThe maximum size (in bytes) that a computer can send or receive in one websocket packet." ) + public int max_websocket_message = ComputerCraft.httpMaxWebsocketMessage; + } + + public static class Peripheral + { + @Comment( "\n\nEnable Command Block peripheral support" ) + public boolean command_block_enabled = ComputerCraft.enableCommandBlock; + + @Comment( "\nThe range of Wireless Modems at low altitude in clear weather, in meters" ) + public int modem_range = ComputerCraft.modem_range; + + @Comment( "\nThe range of Wireless Modems at maximum altitude in clear weather, in meters" ) + public int modem_high_altitude_range = ComputerCraft.modem_highAltitudeRange; + + @Comment( "\nThe range of Wireless Modems at low altitude in stormy weather, in meters" ) + public int modem_range_during_storm = ComputerCraft.modem_rangeDuringStorm; + + @Comment( "\nThe range of Wireless Modems at maximum altitude in stormy weather, in meters" ) + public int modem_high_altitude_range_during_storm = ComputerCraft.modem_highAltitudeRangeDuringStorm; + + @Comment( "\nMaximum amount of notes a speaker can play at once" ) + public int max_notes_per_tick = ComputerCraft.maxNotesPerTick; + } + + public static class Turtle + { + @Comment( "\nSet whether Turtles require fuel to move" ) + public boolean need_fuel = ComputerCraft.turtlesNeedFuel; + + @Comment( "\nThe fuel limit for Turtles" ) + public int normal_fuel_limit = ComputerCraft.turtleFuelLimit; + + @Comment( "\nThe fuel limit for Advanced Turtles" ) + public int advanced_fuel_limit = ComputerCraft.advancedTurtleFuelLimit; + + @Comment( "\nIf set to true, Turtles will be unable to build, dig, or enter protected " + + "areas (such as near the server spawn point)" ) + public boolean obey_block_protection = ComputerCraft.turtlesObeyBlockProtection; + + @Comment( "\nIf set to true, Turtles will push entities out of the way instead of stopping if " + + "there is space to do so" ) + public boolean can_push = ComputerCraft.turtlesCanPush; + + @Comment( "\nA list of turtle actions which are disabled." ) + public String[] disabled_actions = new String[0]; + } + + private static transient Path configPath; + private static transient Config config; + public static final transient Config defaultConfig = new Config(); + + public static Config get() + { + return config; + } + + public static void load( Path path ) + { + configPath = path; + + if( Files.exists( configPath ) ) + { + Jankson jankson = Jankson.builder().build(); + try + { + JsonObject jsonObject = jankson.load( Files.newInputStream( configPath ) ); + config = jankson.fromJson( jsonObject, Config.class ); + } + catch( IOException | SyntaxError e ) + { + config = new Config(); + ComputerCraft.log.error( "Failed to load config! Use default config." ); + e.printStackTrace(); + return; + } + } + else + { + config = new Config(); + } + save(); + sync(); + } + + public static void save() + { + Jankson jankson = Jankson.builder().build(); + try + { + String configData = jankson.toJson( config ).toJson( true, true ); + Files.write( configPath, configData.getBytes() ); + } + catch( IOException e ) + { + ComputerCraft.log.error( "Failed to save config!" ); + e.printStackTrace(); + } + } + + public static void sync() + { + // General + ComputerCraft.computerSpaceLimit = config.general.computer_space_limit; + ComputerCraft.floppySpaceLimit = config.general.floppy_space_limit; + ComputerCraft.maximumFilesOpen = Math.max( 0, config.general.maximum_open_files ); + ComputerCraft.disable_lua51_features = config.general.disable_lua51_features; + ComputerCraft.default_computer_settings = config.general.default_computer_settings; + ComputerCraft.debug_enable = config.general.debug_enabled; + ComputerCraft.logPeripheralErrors = config.general.log_computer_errors; + + // Execution + ComputerCraft.computer_threads = Math.max( 1, config.execution.computer_threads ); + ComputerCraft.maxMainGlobalTime = TimeUnit.MILLISECONDS.toNanos( Math.max( 1, config.execution.max_main_global_time ) ); + ComputerCraft.maxMainComputerTime = TimeUnit.MILLISECONDS.toNanos( Math.max( 1, config.execution.max_main_computer_time ) ); + + // HTTP + ComputerCraft.http_enable = config.http.enabled; + ComputerCraft.http_websocket_enable = config.http.websocket_enabled; + ComputerCraft.http_whitelist = new AddressPredicate( config.http.whitelist ); + ComputerCraft.http_blacklist = new AddressPredicate( config.http.blacklist ); + + ComputerCraft.httpTimeout = Math.max( 0, config.http.timeout ); + ComputerCraft.httpMaxRequests = Math.max( 1, config.http.max_requests ); + ComputerCraft.httpMaxDownload = Math.max( 0, config.http.max_download ); + ComputerCraft.httpMaxUpload = Math.max( 0, config.http.max_upload ); + ComputerCraft.httpMaxWebsockets = Math.max( 1, config.http.max_websockets ); + ComputerCraft.httpMaxWebsocketMessage = Math.min( Math.max( 0, config.http.max_websocket_message ), Websocket.MAX_MESSAGE_SIZE ); + + // Peripheral + ComputerCraft.enableCommandBlock = config.peripheral.command_block_enabled; + ComputerCraft.maxNotesPerTick = Math.max( 1, config.peripheral.max_notes_per_tick ); + ComputerCraft.modem_range = Math.min( Math.max( 0, config.peripheral.modem_range ), MODEM_MAX_RANGE ); + ComputerCraft.modem_highAltitudeRange = Math.min( Math.max( 0, config.peripheral.modem_high_altitude_range ), MODEM_MAX_RANGE ); + ComputerCraft.modem_rangeDuringStorm = Math.min( Math.max( 0, config.peripheral.modem_range_during_storm ), MODEM_MAX_RANGE ); + ComputerCraft.modem_highAltitudeRangeDuringStorm = Math.min( Math.max( 0, config.peripheral.modem_high_altitude_range_during_storm ), MODEM_MAX_RANGE ); + + // Turtles + ComputerCraft.turtlesNeedFuel = config.turtle.need_fuel; + ComputerCraft.turtleFuelLimit = Math.max( 0, config.turtle.normal_fuel_limit ); + ComputerCraft.advancedTurtleFuelLimit = Math.max( 0, config.turtle.advanced_fuel_limit ); + ComputerCraft.turtlesObeyBlockProtection = config.turtle.obey_block_protection; + ComputerCraft.turtlesCanPush = config.turtle.can_push; + + ComputerCraft.turtleDisabledActions.clear(); + Converter converter = CaseFormat.LOWER_CAMEL.converterTo( CaseFormat.UPPER_UNDERSCORE ); + for( String value : config.turtle.disabled_actions ) + { + try + { + ComputerCraft.turtleDisabledActions.add( TurtleAction.valueOf( converter.convert( value ) ) ); + } + catch( IllegalArgumentException e ) + { + ComputerCraft.log.error( "Unknown turtle action " + value ); + } + } + } +} diff --git a/remappedSrc/dan200/computercraft/shared/util/CreativeTabMain.java b/remappedSrc/dan200/computercraft/shared/util/CreativeTabMain.java new file mode 100644 index 000000000..a73a0fb06 --- /dev/null +++ b/remappedSrc/dan200/computercraft/shared/util/CreativeTabMain.java @@ -0,0 +1,31 @@ +/* + * 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.shared.util; + +import dan200.computercraft.ComputerCraft; +import net.fabricmc.api.EnvType; +import net.fabricmc.api.Environment; +import net.minecraft.item.ItemGroup; +import net.minecraft.item.ItemStack; + +import javax.annotation.Nonnull; + +public class CreativeTabMain extends ItemGroup +{ + public CreativeTabMain( int i ) + { + super( i, ComputerCraft.MOD_ID ); + } + + @Nonnull + @Override + @Environment( EnvType.CLIENT ) + public ItemStack createIcon() + { + return new ItemStack( ComputerCraft.Blocks.computerNormal ); + } +} diff --git a/remappedSrc/dan200/computercraft/shared/util/DefaultInventory.java b/remappedSrc/dan200/computercraft/shared/util/DefaultInventory.java new file mode 100644 index 000000000..59e223083 --- /dev/null +++ b/remappedSrc/dan200/computercraft/shared/util/DefaultInventory.java @@ -0,0 +1,13 @@ +/* + * 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.shared.util; + +import net.minecraft.inventory.Inventory; + +public interface DefaultInventory extends Inventory +{ +} diff --git a/remappedSrc/dan200/computercraft/shared/util/DefaultPropertyDelegate.java b/remappedSrc/dan200/computercraft/shared/util/DefaultPropertyDelegate.java new file mode 100644 index 000000000..736b50f6d --- /dev/null +++ b/remappedSrc/dan200/computercraft/shared/util/DefaultPropertyDelegate.java @@ -0,0 +1,17 @@ +/* + * 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.shared.util; + +import net.minecraft.screen.PropertyDelegate; + +public interface DefaultPropertyDelegate extends PropertyDelegate +{ + @Override + default void set( int property, int value ) + { + } +} diff --git a/remappedSrc/dan200/computercraft/shared/util/DefaultSidedInventory.java b/remappedSrc/dan200/computercraft/shared/util/DefaultSidedInventory.java new file mode 100644 index 000000000..bed6dc341 --- /dev/null +++ b/remappedSrc/dan200/computercraft/shared/util/DefaultSidedInventory.java @@ -0,0 +1,29 @@ +/* + * 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.shared.util; + +import net.minecraft.inventory.SidedInventory; +import net.minecraft.item.ItemStack; +import net.minecraft.util.math.Direction; + +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public interface DefaultSidedInventory extends DefaultInventory, SidedInventory +{ + @Override + default boolean canInsert( int slot, @Nonnull ItemStack stack, @Nullable Direction side ) + { + return isValid( slot, stack ); + } + + @Override + default boolean canExtract( int slot, @Nonnull ItemStack stack, @Nonnull Direction side ) + { + return true; + } +} diff --git a/remappedSrc/dan200/computercraft/shared/util/DirectionUtil.java b/remappedSrc/dan200/computercraft/shared/util/DirectionUtil.java new file mode 100644 index 000000000..d38b06406 --- /dev/null +++ b/remappedSrc/dan200/computercraft/shared/util/DirectionUtil.java @@ -0,0 +1,42 @@ +/* + * 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.shared.util; + +import dan200.computercraft.core.computer.ComputerSide; +import net.minecraft.util.math.Direction; + +public final class DirectionUtil +{ + private DirectionUtil() {} + + public static final Direction[] FACINGS = Direction.values(); + + public static ComputerSide toLocal( Direction front, Direction dir ) + { + if( front.getAxis() == Direction.Axis.Y ) front = Direction.NORTH; + + if( dir == front ) return ComputerSide.FRONT; + if( dir == front.getOpposite() ) return ComputerSide.BACK; + if( dir == front.rotateYCounterclockwise() ) return ComputerSide.LEFT; + if( dir == front.rotateYClockwise() ) return ComputerSide.RIGHT; + if( dir == Direction.UP ) return ComputerSide.TOP; + return ComputerSide.BOTTOM; + } + + public static float toPitchAngle( Direction dir ) + { + switch( dir ) + { + case DOWN: + return 90.0f; + case UP: + return 270.0f; + default: + return 0.0f; + } + } +} diff --git a/remappedSrc/dan200/computercraft/shared/util/DropConsumer.java b/remappedSrc/dan200/computercraft/shared/util/DropConsumer.java new file mode 100644 index 000000000..ff72d1694 --- /dev/null +++ b/remappedSrc/dan200/computercraft/shared/util/DropConsumer.java @@ -0,0 +1,127 @@ +/* + * 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.shared.util; + +import net.minecraft.entity.Entity; +import net.minecraft.entity.ItemEntity; +import net.minecraft.item.ItemStack; +import net.minecraft.util.math.BlockPos; +import net.minecraft.util.math.Box; +import net.minecraft.world.World; + +import java.lang.ref.WeakReference; +import java.util.ArrayList; +import java.util.List; +import java.util.function.Function; + +public final class DropConsumer +{ + private DropConsumer() + { + } + + private static Function dropConsumer; + private static List remainingDrops; + private static WeakReference dropWorld; + private static BlockPos dropPos; + private static Box dropBounds; + private static WeakReference dropEntity; + + public static void set( Entity entity, Function consumer ) + { + dropConsumer = consumer; + remainingDrops = new ArrayList<>(); + dropEntity = new WeakReference<>( entity ); + dropWorld = new WeakReference<>( entity.world ); + dropPos = null; + dropBounds = new Box( entity.getBlockPos() ).expand( 2, 2, 2 ); + + // entity.captureDrops( new ArrayList<>() ); + } + + public static void set( World world, BlockPos pos, Function consumer ) + { + dropConsumer = consumer; + remainingDrops = new ArrayList<>(); + dropEntity = null; + dropWorld = new WeakReference<>( world ); + dropPos = pos; + dropBounds = new Box( pos ).expand( 2, 2, 2 ); + } + + public static List clear() + { + /* + if( dropEntity != null ) + { + Entity entity = dropEntity.get(); + if( entity != null ) + { + Collection dropped = entity.captureDrops( null ); + if( dropped != null ) + { + for( EntityItem entityItem : dropped ) handleDrops( entityItem.getItem() ); + } + } + } + */ + + List remainingStacks = remainingDrops; + + dropConsumer = null; + remainingDrops = null; + dropEntity = null; + dropWorld = null; + dropPos = null; + dropBounds = null; + + return remainingStacks; + } + + private static void handleDrops( ItemStack stack ) + { + ItemStack remaining = dropConsumer.apply( stack ); + if( !remaining.isEmpty() ) remainingDrops.add( remaining ); + } + + public static boolean onEntityLivingDrops( Entity entity, ItemStack stack ) + { + // Capture any mob drops for the current entity + if( dropEntity != null && entity == dropEntity.get() ) + { + handleDrops( stack ); + return true; + } + + return false; + } + + public static boolean onHarvestDrops( World world, BlockPos pos, ItemStack stack ) + { + // Capture block drops for the current entity + if( dropWorld != null && dropWorld.get() == world && dropPos != null && dropPos.equals( pos ) ) + { + handleDrops( stack ); + return true; + } + + return false; + } + + public static boolean onEntitySpawn( Entity entity ) + { + // Capture any nearby item spawns + if( dropWorld != null && dropWorld.get() == entity.getEntityWorld() && entity instanceof ItemEntity + && dropBounds.contains( entity.getPos() ) ) + { + handleDrops( ((ItemEntity) entity).getStack() ); + return true; + } + + return false; + } +} diff --git a/remappedSrc/dan200/computercraft/shared/util/Holiday.java b/remappedSrc/dan200/computercraft/shared/util/Holiday.java new file mode 100644 index 000000000..a59674a01 --- /dev/null +++ b/remappedSrc/dan200/computercraft/shared/util/Holiday.java @@ -0,0 +1,16 @@ +/* + * 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.shared.util; + +public enum Holiday +{ + None, + Valentines, + AprilFoolsDay, + Halloween, + Christmas, +} diff --git a/remappedSrc/dan200/computercraft/shared/util/HolidayUtil.java b/remappedSrc/dan200/computercraft/shared/util/HolidayUtil.java new file mode 100644 index 000000000..4212a8af0 --- /dev/null +++ b/remappedSrc/dan200/computercraft/shared/util/HolidayUtil.java @@ -0,0 +1,30 @@ +/* + * 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.shared.util; + +import java.util.Calendar; + +public final class HolidayUtil +{ + private HolidayUtil() {} + + public static Holiday getCurrentHoliday() + { + return getHoliday( Calendar.getInstance() ); + } + + private static Holiday getHoliday( Calendar calendar ) + { + int month = calendar.get( Calendar.MONTH ); + int day = calendar.get( Calendar.DAY_OF_MONTH ); + if( month == Calendar.FEBRUARY && day == 14 ) return Holiday.Valentines; + if( month == Calendar.APRIL && day == 1 ) return Holiday.AprilFoolsDay; + if( month == Calendar.OCTOBER && day == 31 ) return Holiday.Halloween; + if( month == Calendar.DECEMBER && day >= 24 && day <= 30 ) return Holiday.Christmas; + return Holiday.None; + } +} diff --git a/remappedSrc/dan200/computercraft/shared/util/IDAssigner.java b/remappedSrc/dan200/computercraft/shared/util/IDAssigner.java new file mode 100644 index 000000000..b56dccf8e --- /dev/null +++ b/remappedSrc/dan200/computercraft/shared/util/IDAssigner.java @@ -0,0 +1,106 @@ +/* + * 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.shared.util; + +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; +import com.google.gson.reflect.TypeToken; +import dan200.computercraft.ComputerCraft; +import net.minecraft.server.MinecraftServer; +import net.minecraft.world.World; +import net.minecraft.world.dimension.DimensionType; + +import java.io.File; +import java.io.Reader; +import java.io.Writer; +import java.lang.ref.WeakReference; +import java.lang.reflect.Type; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.HashMap; +import java.util.Map; + +public final class IDAssigner +{ + private static final Gson GSON = new GsonBuilder().setPrettyPrinting().create(); + private static final Type ID_TOKEN = new TypeToken>() {}.getType(); + + private IDAssigner() + { + } + + private static Map ids; + private static WeakReference server; + private static Path idFile; + + public static File getDir( World world ) + { + MinecraftServer server = world.getServer(); + File worldDirectory = server.getWorld( DimensionType.OVERWORLD ).getSaveHandler().getWorldDir(); + return new File( worldDirectory, ComputerCraft.MOD_ID ); + } + + private static MinecraftServer getCachedServer( World world ) + { + if( server == null ) return null; + + MinecraftServer currentServer = server.get(); + if( currentServer == null ) return null; + + if( currentServer != world.getServer() ) return null; + return currentServer; + } + + public static synchronized int getNextId( World world, String kind ) + { + MinecraftServer currentServer = getCachedServer( world ); + if( currentServer == null ) + { + // The server has changed, refetch our ID map + server = new WeakReference<>( world.getServer() ); + + File dir = getDir( world ); + dir.mkdirs(); + + // Load our ID file from disk + idFile = new File( dir, "ids.json" ).toPath(); + if( Files.isRegularFile( idFile ) ) + { + try( Reader reader = Files.newBufferedReader( idFile, StandardCharsets.UTF_8 ) ) + { + ids = GSON.fromJson( reader, ID_TOKEN ); + } + catch( Exception e ) + { + ComputerCraft.log.error( "Cannot load id file '" + idFile + "'", e ); + ids = new HashMap<>(); + } + } + else + { + ids = new HashMap<>(); + } + } + + Integer existing = ids.get( kind ); + int next = existing == null ? 0 : existing + 1; + ids.put( kind, next ); + + // We've changed the ID file, so save it back again. + try( Writer writer = Files.newBufferedWriter( idFile, StandardCharsets.UTF_8 ) ) + { + GSON.toJson( ids, writer ); + } + catch( Exception e ) + { + ComputerCraft.log.error( "Cannot update ID file '" + idFile + "'", e ); + } + + return next; + } +} diff --git a/remappedSrc/dan200/computercraft/shared/util/ImpostorRecipe.java b/remappedSrc/dan200/computercraft/shared/util/ImpostorRecipe.java new file mode 100644 index 000000000..8c419fbaa --- /dev/null +++ b/remappedSrc/dan200/computercraft/shared/util/ImpostorRecipe.java @@ -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.shared.util; + +import com.google.gson.JsonObject; +import dan200.computercraft.ComputerCraft; +import net.minecraft.inventory.CraftingInventory; +import net.minecraft.item.ItemStack; +import net.minecraft.network.PacketByteBuf; +import net.minecraft.recipe.Ingredient; +import net.minecraft.recipe.RecipeSerializer; +import net.minecraft.recipe.ShapedRecipe; +import net.minecraft.util.Identifier; +import net.minecraft.util.JsonHelper; +import net.minecraft.util.collection.DefaultedList; +import net.minecraft.world.World; + +import javax.annotation.Nonnull; + +public final class ImpostorRecipe extends ShapedRecipe +{ + private final String group; + + private ImpostorRecipe( @Nonnull Identifier id, @Nonnull String group, int width, int height, DefaultedList ingredients, @Nonnull ItemStack result ) + { + super( id, group, width, height, ingredients, result ); + this.group = group; + } + + @Nonnull + @Override + public String getGroup() + { + return group; + } + + @Override + public boolean matches( @Nonnull CraftingInventory inv, World world ) + { + return false; + } + + @Nonnull + @Override + public ItemStack craft( @Nonnull CraftingInventory inventory ) + { + return ItemStack.EMPTY; + } + + @Nonnull + @Override + public RecipeSerializer getSerializer() + { + return SERIALIZER; + } + + private static final Identifier ID = new Identifier( ComputerCraft.MOD_ID, "impostor_shaped" ); + public static final RecipeSerializer SERIALIZER = new RecipeSerializer() + { + @Override + public ImpostorRecipe read( Identifier identifier, JsonObject json ) + { + String group = JsonHelper.getString( json, "group", "" ); + ShapedRecipe recipe = RecipeSerializer.SHAPED.read( identifier, json ); + ItemStack result = ShapedRecipe.getItemStack( JsonHelper.getObject( json, "result" ) ); + return new ImpostorRecipe( identifier, group, recipe.getWidth(), recipe.getHeight(), recipe.getPreviewInputs(), result ); + } + + @Override + public ImpostorRecipe read( Identifier identifier, PacketByteBuf buf ) + { + int width = buf.readVarInt(); + int height = buf.readVarInt(); + String group = buf.readString( Short.MAX_VALUE ); + DefaultedList items = DefaultedList.ofSize( width * height, Ingredient.EMPTY ); + for( int k = 0; k < items.size(); ++k ) items.set( k, Ingredient.fromPacket( buf ) ); + ItemStack result = buf.readItemStack(); + return new ImpostorRecipe( identifier, group, width, height, items, result ); + } + + @Override + public void write( @Nonnull PacketByteBuf buf, @Nonnull ImpostorRecipe recipe ) + { + buf.writeVarInt( recipe.getWidth() ); + buf.writeVarInt( recipe.getHeight() ); + buf.writeString( recipe.getGroup() ); + for( Ingredient ingredient : recipe.getPreviewInputs() ) ingredient.write( buf ); + buf.writeItemStack( recipe.getOutput() ); + } + }; +} diff --git a/remappedSrc/dan200/computercraft/shared/util/ImpostorShapelessRecipe.java b/remappedSrc/dan200/computercraft/shared/util/ImpostorShapelessRecipe.java new file mode 100644 index 000000000..3ce3233d3 --- /dev/null +++ b/remappedSrc/dan200/computercraft/shared/util/ImpostorShapelessRecipe.java @@ -0,0 +1,116 @@ +/* + * 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.shared.util; + +import com.google.gson.JsonArray; +import com.google.gson.JsonObject; +import com.google.gson.JsonParseException; +import net.minecraft.inventory.CraftingInventory; +import net.minecraft.item.ItemStack; +import net.minecraft.network.PacketByteBuf; +import net.minecraft.recipe.Ingredient; +import net.minecraft.recipe.RecipeSerializer; +import net.minecraft.recipe.ShapedRecipe; +import net.minecraft.recipe.ShapelessRecipe; +import net.minecraft.util.Identifier; +import net.minecraft.util.JsonHelper; +import net.minecraft.util.collection.DefaultedList; +import net.minecraft.world.World; + +import javax.annotation.Nonnull; + +public final class ImpostorShapelessRecipe extends ShapelessRecipe +{ + private final String group; + + private ImpostorShapelessRecipe( @Nonnull Identifier id, @Nonnull String group, @Nonnull ItemStack result, DefaultedList ingredients ) + { + super( id, group, result, ingredients ); + this.group = group; + } + + @Nonnull + @Override + public String getGroup() + { + return group; + } + + @Override + public boolean matches( CraftingInventory inv, World world ) + { + return false; + } + + @Nonnull + @Override + public ItemStack craft( CraftingInventory inventory ) + { + return ItemStack.EMPTY; + } + + @Nonnull + @Override + public RecipeSerializer getSerializer() + { + return SERIALIZER; + } + + public static final RecipeSerializer SERIALIZER = new RecipeSerializer() + { + @Override + public ImpostorShapelessRecipe read( @Nonnull Identifier id, @Nonnull JsonObject json ) + { + String s = JsonHelper.getString( json, "group", "" ); + DefaultedList ingredients = readIngredients( JsonHelper.getArray( json, "ingredients" ) ); + + if( ingredients.isEmpty() ) throw new JsonParseException( "No ingredients for shapeless recipe" ); + if( ingredients.size() > 9 ) + { + throw new JsonParseException( "Too many ingredients for shapeless recipe the max is 9" ); + } + + ItemStack itemstack = ShapedRecipe.getItemStack( JsonHelper.getObject( json, "result" ) ); + return new ImpostorShapelessRecipe( id, s, itemstack, ingredients ); + } + + private DefaultedList readIngredients( JsonArray arrays ) + { + DefaultedList items = DefaultedList.of(); + for( int i = 0; i < arrays.size(); ++i ) + { + Ingredient ingredient = Ingredient.fromJson( arrays.get( i ) ); + if( !ingredient.isEmpty() ) items.add( ingredient ); + } + + return items; + } + + @Override + public ImpostorShapelessRecipe read( @Nonnull Identifier id, PacketByteBuf buffer ) + { + String s = buffer.readString( 32767 ); + int i = buffer.readVarInt(); + DefaultedList items = DefaultedList.ofSize( i, Ingredient.EMPTY ); + + for( int j = 0; j < items.size(); j++ ) items.set( j, Ingredient.fromPacket( buffer ) ); + ItemStack result = buffer.readItemStack(); + + return new ImpostorShapelessRecipe( id, s, result, items ); + } + + @Override + public void write( @Nonnull PacketByteBuf buffer, @Nonnull ImpostorShapelessRecipe recipe ) + { + buffer.writeString( recipe.getGroup() ); + buffer.writeVarInt( recipe.getPreviewInputs().size() ); + + for( Ingredient ingredient : recipe.getPreviewInputs() ) ingredient.write( buffer ); + buffer.writeItemStack( recipe.getOutput() ); + } + }; +} diff --git a/remappedSrc/dan200/computercraft/shared/util/InventoryUtil.java b/remappedSrc/dan200/computercraft/shared/util/InventoryUtil.java new file mode 100644 index 000000000..e8c4c84f1 --- /dev/null +++ b/remappedSrc/dan200/computercraft/shared/util/InventoryUtil.java @@ -0,0 +1,189 @@ +/* + * 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.shared.util; + +import net.minecraft.block.entity.BlockEntity; +import net.minecraft.entity.Entity; +import net.minecraft.inventory.Inventory; +import net.minecraft.item.ItemStack; +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 org.apache.commons.lang3.tuple.Pair; + +import javax.annotation.Nonnull; + +public final class InventoryUtil +{ + private InventoryUtil() {} + // Methods for comparing things: + + public static boolean areItemsEqual( @Nonnull ItemStack a, @Nonnull ItemStack b ) + { + return a == b || ItemStack.areEqual( a, b ); + } + + public static boolean areItemsStackable( @Nonnull ItemStack a, @Nonnull ItemStack b ) + { + return a == b || (a.getItem() == b.getItem() && ItemStack.areTagsEqual( a, b )); + } + + /** + * Determines if two items are "mostly" equivalent. Namely, they have the same item and damage, and identical + * share stacks. + * + * This is largely based on {@link net.minecraftforge.common.crafting.IngredientNBT#test(ItemStack)}. It is + * sufficient to ensure basic information (such as enchantments) are the same, while not having to worry about + * capabilities. + * + * @param a The first stack to check + * @param b The second stack to check + * @return If these items are largely the same. + */ + public static boolean areItemsSimilar( @Nonnull ItemStack a, @Nonnull ItemStack b ) + { + if( a == b ) return true; + if( a.isEmpty() ) return !b.isEmpty(); + + if( a.getItem() != b.getItem() ) return false; + + // A more expanded form of ItemStack.areShareTagsEqual, but allowing an empty tag to be equal to a + // null one. + CompoundTag shareTagA = a.getTag(); + CompoundTag shareTagB = b.getTag(); + if( shareTagA == shareTagB ) return true; + if( shareTagA == null ) return shareTagB.isEmpty(); + if( shareTagB == null ) return shareTagA.isEmpty(); + return shareTagA.equals( shareTagB ); + } + + @Nonnull + public static ItemStack copyItem( @Nonnull ItemStack a ) + { + return a.copy(); + } + + // Methods for finding inventories: + + public static Inventory getInventory( World world, BlockPos pos, Direction side ) + { + // Look for tile with inventory + int y = pos.getY(); + if( y >= 0 && y < world.getHeight() ) + { + BlockEntity tileEntity = world.getBlockEntity( pos ); + if( tileEntity instanceof Inventory ) + { + return (Inventory) tileEntity; + } + } + + // Look for entity with inventory + Vec3d vecStart = new Vec3d( + pos.getX() + 0.5 + 0.6 * side.getOffsetX(), + pos.getY() + 0.5 + 0.6 * side.getOffsetY(), + pos.getZ() + 0.5 + 0.6 * side.getOffsetZ() + ); + Direction dir = side.getOpposite(); + Vec3d vecDir = new Vec3d( + dir.getOffsetX(), dir.getOffsetY(), dir.getOffsetZ() + ); + Pair hit = WorldUtil.rayTraceEntities( world, vecStart, vecDir, 1.1 ); + if( hit != null ) + { + Entity entity = hit.getKey(); + if( entity instanceof Inventory ) + { + return (Inventory) entity; + } + } + return null; + } + + public static ItemStorage getStorage( World world, BlockPos pos, Direction side ) + { + Inventory inventory = getInventory( world, pos, side ); + return inventory == null ? null : ItemStorage.wrap( inventory, side ); + } + + // Methods for placing into inventories: + + @Nonnull + public static ItemStack storeItems( @Nonnull ItemStack itemstack, ItemStorage inventory, int begin ) + { + return storeItems( itemstack, inventory, 0, inventory.size(), begin ); + } + + @Nonnull + public static ItemStack storeItems( @Nonnull ItemStack itemstack, ItemStorage inventory ) + { + return storeItems( itemstack, inventory, 0, inventory.size(), 0 ); + } + + @Nonnull + public static ItemStack storeItems( @Nonnull ItemStack stack, ItemStorage inventory, int start, int range, int begin ) + { + if( stack.isEmpty() ) return ItemStack.EMPTY; + + // Inspect the slots in order and try to find empty or stackable slots + ItemStack remainder = stack.copy(); + for( int i = 0; i < range; i++ ) + { + int slot = start + (i + begin - start) % range; + if( remainder.isEmpty() ) break; + remainder = inventory.store( slot, remainder, false ); + } + return areItemsEqual( stack, remainder ) ? stack : remainder; + } + + // Methods for taking out of inventories + + @Nonnull + public static ItemStack takeItems( int count, ItemStorage inventory, int begin ) + { + return takeItems( count, inventory, 0, inventory.size(), begin ); + } + + @Nonnull + public static ItemStack takeItems( int count, ItemStorage inventory ) + { + return takeItems( count, inventory, 0, inventory.size(), 0 ); + } + + @Nonnull + public static ItemStack takeItems( int count, ItemStorage inventory, int start, int range, int begin ) + { + // Combine multiple stacks from inventory into one if necessary + ItemStack partialStack = ItemStack.EMPTY; + for( int i = 0; i < range; i++ ) + { + int slot = start + (i + begin - start) % range; + + if( count <= 0 ) break; + + // If this doesn't slot, abort. + ItemStack extracted = inventory.take( slot, count, partialStack, false ); + if( extracted.isEmpty() ) continue; + + count -= extracted.getCount(); + if( partialStack.isEmpty() ) + { + // If we've extracted for this first time, then limit the count to the maximum stack size. + partialStack = extracted; + count = Math.min( count, extracted.getMaxCount() ); + } + else + { + partialStack.increment( extracted.getCount() ); + } + } + + return partialStack; + } +} diff --git a/remappedSrc/dan200/computercraft/shared/util/IoUtil.java b/remappedSrc/dan200/computercraft/shared/util/IoUtil.java new file mode 100644 index 000000000..a4fc13638 --- /dev/null +++ b/remappedSrc/dan200/computercraft/shared/util/IoUtil.java @@ -0,0 +1,26 @@ +/* + * 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.shared.util; + +import java.io.Closeable; +import java.io.IOException; + +public final class IoUtil +{ + private IoUtil() {} + + public static void closeQuietly( Closeable closeable ) + { + try + { + closeable.close(); + } + catch( IOException ignored ) + { + } + } +} diff --git a/remappedSrc/dan200/computercraft/shared/util/ItemStorage.java b/remappedSrc/dan200/computercraft/shared/util/ItemStorage.java new file mode 100644 index 000000000..c218ae9bc --- /dev/null +++ b/remappedSrc/dan200/computercraft/shared/util/ItemStorage.java @@ -0,0 +1,256 @@ +/* + * 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.shared.util; + +import net.minecraft.inventory.Inventory; +import net.minecraft.inventory.SidedInventory; +import net.minecraft.item.ItemStack; +import net.minecraft.util.math.Direction; + +import javax.annotation.Nonnull; + +/** + * The most cutesy alternative of {@code IItemHandler} the world has ever seen. + */ +public interface ItemStorage +{ + int size(); + + @Nonnull + ItemStack take( int slot, int limit, @Nonnull ItemStack filter, boolean simulate ); + + @Nonnull + ItemStack store( int slot, @Nonnull ItemStack stack, boolean simulate ); + + default ItemStorage view( int start, int size ) + { + return new View( this, start, size ); + } + + class InventoryWrapper implements ItemStorage + { + private final Inventory inventory; + + InventoryWrapper( Inventory inventory ) + { + this.inventory = inventory; + } + + private void setAndDirty( int slot, @Nonnull ItemStack stack ) + { + inventory.setStack( slot, stack ); + inventory.markDirty(); + } + + protected boolean canExtract( int slot, ItemStack stack ) + { + return true; + } + + @Override + public int size() + { + return inventory.size(); + } + + @Override + @Nonnull + public ItemStack take( int slot, int limit, @Nonnull ItemStack filter, boolean simulate ) + { + ItemStack existing = inventory.getStack( slot ); + if( existing.isEmpty() || !canExtract( slot, existing ) + || (!filter.isEmpty() && !areStackable( existing, filter )) ) + { + return ItemStack.EMPTY; + } + + if( simulate ) + { + existing = existing.copy(); + if( existing.getCount() > limit ) existing.setCount( limit ); + return existing; + } + else if( existing.getCount() < limit ) + { + setAndDirty( slot, ItemStack.EMPTY ); + return existing; + } + else + { + ItemStack result = existing.split( limit ); + setAndDirty( slot, existing ); + return result; + } + } + + @Override + @Nonnull + public ItemStack store( int slot, @Nonnull ItemStack stack, boolean simulate ) + { + if( stack.isEmpty() || !inventory.isValid( slot, stack ) ) return stack; + + ItemStack existing = inventory.getStack( slot ); + if( existing.isEmpty() ) + { + int limit = Math.min( stack.getMaxCount(), inventory.getMaxCountPerStack() ); + if( limit <= 0 ) return stack; + + if( stack.getCount() < limit ) + { + if( !simulate ) setAndDirty( slot, stack ); + return ItemStack.EMPTY; + } + else + { + stack = stack.copy(); + ItemStack insert = stack.split( limit ); + if( !simulate ) setAndDirty( slot, insert ); + return stack; + } + } + else if( areStackable( stack, existing ) ) + { + int limit = Math.min( existing.getMaxCount(), inventory.getMaxCountPerStack() ) - existing.getCount(); + if( limit <= 0 ) return stack; + + if( stack.getCount() < limit ) + { + if( !simulate ) + { + existing.increment( stack.getCount() ); + setAndDirty( slot, existing ); + } + return ItemStack.EMPTY; + } + else + { + stack = stack.copy(); + stack.decrement( limit ); + if( !simulate ) + { + existing.increment( limit ); + setAndDirty( slot, existing ); + } + return stack; + } + } + else + { + return stack; + } + } + } + + class SidedInventoryWrapper extends InventoryWrapper + { + private final SidedInventory inventory; + private final Direction facing; + + SidedInventoryWrapper( SidedInventory inventory, Direction facing ) + { + super( inventory ); + this.inventory = inventory; + this.facing = facing; + } + + @Override + public int size() + { + return inventory.getAvailableSlots( facing ).length; + } + + @Override + protected boolean canExtract( int slot, ItemStack stack ) + { + return super.canExtract( slot, stack ) && inventory.canExtract( slot, stack, facing ); + } + + @Nonnull + @Override + public ItemStack take( int slot, int limit, @Nonnull ItemStack filter, boolean simulate ) + { + int[] slots = inventory.getAvailableSlots( facing ); + return slot >= 0 && slot < slots.length ? super.take( slots[slot], limit, filter, simulate ) : ItemStack.EMPTY; + } + + @Nonnull + @Override + public ItemStack store( int slot, @Nonnull ItemStack stack, boolean simulate ) + { + int[] slots = inventory.getAvailableSlots( facing ); + if( slot < 0 || slot >= slots.length ) return stack; + + int mappedSlot = slots[slot]; + if( !inventory.canInsert( slot, stack, facing ) ) return stack; + return super.store( mappedSlot, stack, simulate ); + } + } + + class View implements ItemStorage + { + private final ItemStorage parent; + private final int start; + private final int size; + + View( ItemStorage parent, int start, int size ) + { + this.parent = parent; + this.start = start; + this.size = size; + } + + @Override + public int size() + { + return size; + } + + @Nonnull + @Override + public ItemStack take( int slot, int limit, @Nonnull ItemStack filter, boolean simulate ) + { + if( slot < start || slot >= start + size ) return ItemStack.EMPTY; + return parent.take( slot - start, limit, filter, simulate ); + } + + @Nonnull + @Override + public ItemStack store( int slot, @Nonnull ItemStack stack, boolean simulate ) + { + if( slot < start || slot >= start + size ) return stack; + return parent.store( slot - start, stack, simulate ); + } + + @Override + public ItemStorage view( int start, int size ) + { + return new View( this.parent, this.start + start, size ); + } + } + + static ItemStorage wrap( Inventory inventory ) + { + return new InventoryWrapper( inventory ); + } + + static ItemStorage wrap( @Nonnull SidedInventory inventory, @Nonnull Direction facing ) + { + return new SidedInventoryWrapper( inventory, facing ); + } + + static ItemStorage wrap( @Nonnull Inventory inventory, @Nonnull Direction facing ) + { + return inventory instanceof SidedInventory + ? new SidedInventoryWrapper( (SidedInventory) inventory, facing ) + : new InventoryWrapper( inventory ); + } + + static boolean areStackable( @Nonnull ItemStack a, @Nonnull ItemStack b ) + { + return a == b || (a.getItem() == b.getItem() && ItemStack.areTagsEqual( a, b )); + } +} diff --git a/remappedSrc/dan200/computercraft/shared/util/NBTUtil.java b/remappedSrc/dan200/computercraft/shared/util/NBTUtil.java new file mode 100644 index 000000000..b0ae60328 --- /dev/null +++ b/remappedSrc/dan200/computercraft/shared/util/NBTUtil.java @@ -0,0 +1,175 @@ +/* + * 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.shared.util; + +import net.minecraft.nbt.*; + +import java.util.HashMap; +import java.util.Map; + +public final class NBTUtil +{ + public static final int TAG_END = 0; + public static final int TAG_BYTE = 1; + public static final int TAG_SHORT = 2; + public static final int TAG_INT = 3; + public static final int TAG_LONG = 4; + public static final int TAG_FLOAT = 5; + public static final int TAG_DOUBLE = 6; + public static final int TAG_BYTE_ARRAY = 7; + public static final int TAG_STRING = 8; + public static final int TAG_LIST = 9; + public static final int TAG_COMPOUND = 10; + public static final int TAG_INT_ARRAY = 11; + public static final int TAG_LONG_ARRAY = 12; + public static final int TAG_ANY_NUMERIC = 99; + + private NBTUtil() {} + + private static Tag toNBTTag( Object object ) + { + if( object == null ) return null; + if( object instanceof Boolean ) return new ByteTag( (byte) ((boolean) (Boolean) object ? 1 : 0) ); + if( object instanceof Number ) return new DoubleTag( ((Number) object).doubleValue() ); + if( object instanceof String ) return new StringTag( object.toString() ); + if( object instanceof Map ) + { + Map m = (Map) object; + CompoundTag nbt = new CompoundTag(); + int i = 0; + for( Map.Entry entry : m.entrySet() ) + { + Tag key = toNBTTag( entry.getKey() ); + Tag value = toNBTTag( entry.getKey() ); + if( key != null && value != null ) + { + nbt.put( "k" + i, key ); + nbt.put( "v" + i, value ); + i++; + } + } + nbt.putInt( "len", m.size() ); + return nbt; + } + + return null; + } + + public static CompoundTag encodeObjects( Object[] objects ) + { + if( objects == null || objects.length <= 0 ) return null; + + CompoundTag nbt = new CompoundTag(); + nbt.putInt( "len", objects.length ); + for( int i = 0; i < objects.length; i++ ) + { + Tag child = toNBTTag( objects[i] ); + if( child != null ) nbt.put( Integer.toString( i ), child ); + } + return nbt; + } + + private static Object fromNBTTag( Tag tag ) + { + if( tag == null ) return null; + switch( tag.getType() ) + { + case TAG_BYTE: + return ((ByteTag) tag).getByte() > 0; + case TAG_DOUBLE: + return ((DoubleTag) tag).getDouble(); + default: + case TAG_STRING: + return tag.asString(); + case TAG_COMPOUND: + { + CompoundTag c = (CompoundTag) tag; + int len = c.getInt( "len" ); + Map map = new HashMap<>( len ); + for( int i = 0; i < len; i++ ) + { + Object key = fromNBTTag( c.get( "k" + i ) ); + Object value = fromNBTTag( c.get( "v" + i ) ); + if( key != null && value != null ) map.put( key, value ); + } + return map; + } + } + } + + public static Object toLua( Tag tag ) + { + if( tag == null ) return null; + + byte typeID = tag.getType(); + switch( typeID ) + { + case TAG_BYTE: + case TAG_SHORT: + case TAG_INT: + case TAG_LONG: + return ((AbstractNumberTag) tag).getLong(); + case TAG_FLOAT: + case TAG_DOUBLE: + return ((AbstractNumberTag) tag).getDouble(); + case TAG_STRING: // String + return tag.asString(); + case TAG_COMPOUND: // Compound + { + CompoundTag compound = (CompoundTag) tag; + Map map = new HashMap<>( compound.getSize() ); + for( String key : compound.getKeys() ) + { + Object value = toLua( compound.get( key ) ); + if( value != null ) map.put( key, value ); + } + return map; + } + case TAG_LIST: + { + ListTag list = (ListTag) tag; + Map map = new HashMap<>( list.size() ); + for( int i = 0; i < list.size(); i++ ) map.put( i, toLua( list.get( i ) ) ); + return map; + } + case TAG_BYTE_ARRAY: + { + byte[] array = ((ByteArrayTag) tag).getByteArray(); + Map map = new HashMap<>( array.length ); + for( int i = 0; i < array.length; i++ ) map.put( i + 1, array[i] ); + return map; + } + case TAG_INT_ARRAY: + { + int[] array = ((IntArrayTag) tag).getIntArray(); + Map map = new HashMap<>( array.length ); + for( int i = 0; i < array.length; i++ ) map.put( i + 1, array[i] ); + return map; + } + + default: + return null; + } + } + + public static Object[] decodeObjects( CompoundTag tag ) + { + int len = tag.getInt( "len" ); + if( len <= 0 ) return null; + + Object[] objects = new Object[len]; + for( int i = 0; i < len; i++ ) + { + String key = Integer.toString( i ); + if( tag.contains( key ) ) + { + objects[i] = fromNBTTag( tag.get( key ) ); + } + } + return objects; + } +} diff --git a/remappedSrc/dan200/computercraft/shared/util/NamedBlockEntityType.java b/remappedSrc/dan200/computercraft/shared/util/NamedBlockEntityType.java new file mode 100644 index 000000000..8d2df538a --- /dev/null +++ b/remappedSrc/dan200/computercraft/shared/util/NamedBlockEntityType.java @@ -0,0 +1,103 @@ +/* + * 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.shared.util; + +import com.mojang.datafixers.DataFixUtils; +import com.mojang.datafixers.types.Type; +import dan200.computercraft.ComputerCraft; +import net.minecraft.SharedConstants; +import net.minecraft.block.Block; +import net.minecraft.block.entity.BlockEntity; +import net.minecraft.block.entity.BlockEntityType; +import net.minecraft.datafixer.Schemas; +import net.minecraft.datafixer.TypeReferences; +import net.minecraft.util.Identifier; +import net.minecraft.util.registry.MutableRegistry; + +import javax.annotation.Nonnull; +import java.util.Collections; +import java.util.Objects; +import java.util.function.Function; +import java.util.function.Supplier; + +public final class NamedBlockEntityType extends BlockEntityType +{ + private final Identifier identifier; + private Block block; + + private NamedBlockEntityType( Identifier identifier, Supplier supplier ) + { + super( supplier, Collections.emptySet(), getDatafixer( identifier ) ); + this.identifier = identifier; + } + + public static NamedBlockEntityType create( Identifier identifier, Supplier supplier ) + { + return new NamedBlockEntityType<>( identifier, supplier ); + } + + public static NamedBlockEntityType create( Identifier identifier, Function, ? extends T> builder ) + { + return new FixedPointSupplier( identifier, builder ).factory; + } + + public Identifier getId() + { + return identifier; + } + + @Override + public boolean supports( Block block ) + { + return block == this.block; + } + + public void setBlock( @Nonnull Block block ) + { + if( this.block != null ) throw new IllegalStateException( "Cannot override block" ); + this.block = Objects.requireNonNull( block, "block cannot be null" ); + } + + public void register( MutableRegistry> registry ) + { + registry.add( getId(), this ); + } + + public static Type getDatafixer( Identifier id ) + { + try + { + return Schemas.getFixer() + .getSchema( DataFixUtils.makeKey( ComputerCraft.DATAFIXER_VERSION ) ) + .getChoiceType( TypeReferences.BLOCK_ENTITY, id.toString() ); + } + catch( IllegalArgumentException e ) + { + if( SharedConstants.isDevelopment ) throw e; + ComputerCraft.log.warn( "No data fixer registered for block entity " + id ); + return null; + } + } + + private static final class FixedPointSupplier implements Supplier + { + final NamedBlockEntityType factory; + private final Function, ? extends T> builder; + + private FixedPointSupplier( Identifier identifier, Function, ? extends T> builder ) + { + factory = create( identifier, this ); + this.builder = builder; + } + + @Override + public T get() + { + return builder.apply( factory ); + } + } +} diff --git a/remappedSrc/dan200/computercraft/shared/util/Palette.java b/remappedSrc/dan200/computercraft/shared/util/Palette.java new file mode 100644 index 000000000..7df7215d3 --- /dev/null +++ b/remappedSrc/dan200/computercraft/shared/util/Palette.java @@ -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.shared.util; + +import net.minecraft.nbt.CompoundTag; + +public class Palette +{ + private static final int PALETTE_SIZE = 16; + private final double[][] colours = new double[PALETTE_SIZE][3]; + + public static final Palette DEFAULT = new Palette(); + + public Palette() + { + // Get the default palette + resetColours(); + } + + public void setColour( int i, double r, double g, double b ) + { + if( i >= 0 && i < colours.length ) + { + colours[i][0] = r; + colours[i][1] = g; + colours[i][2] = b; + } + } + + public void setColour( int i, Colour colour ) + { + setColour( i, colour.getR(), colour.getG(), colour.getB() ); + } + + public double[] getColour( int i ) + { + if( i >= 0 && i < colours.length ) + { + return colours[i]; + } + return null; + } + + public void resetColour( int i ) + { + if( i >= 0 && i < colours.length ) + { + setColour( i, Colour.values()[i] ); + } + } + + public void resetColours() + { + for( int i = 0; i < Colour.values().length; i++ ) + { + resetColour( i ); + } + } + + public static int encodeRGB8( double[] rgb ) + { + int r = (int) (rgb[0] * 255) & 0xFF; + int g = (int) (rgb[1] * 255) & 0xFF; + int b = (int) (rgb[2] * 255) & 0xFF; + + return (r << 16) | (g << 8) | b; + } + + public static double[] decodeRGB8( int rgb ) + { + return new double[] + { + ((rgb >> 16) & 0xFF) / 255.0f, + ((rgb >> 8) & 0xFF) / 255.0f, + (rgb & 0xFF) / 255.0f + }; + } + + public CompoundTag writeToNBT( CompoundTag nbt ) + { + int[] rgb8 = new int[colours.length]; + + for( int i = 0; i < colours.length; i++ ) + { + rgb8[i] = encodeRGB8( colours[i] ); + } + + nbt.putIntArray( "term_palette", rgb8 ); + return nbt; + } + + public void readFromNBT( CompoundTag nbt ) + { + if( !nbt.contains( "term_palette" ) ) return; + int[] rgb8 = nbt.getIntArray( "term_palette" ); + + if( rgb8.length != colours.length ) return; + + for( int i = 0; i < colours.length; i++ ) + { + colours[i] = decodeRGB8( rgb8[i] ); + } + } +} diff --git a/remappedSrc/dan200/computercraft/shared/util/RecipeUtil.java b/remappedSrc/dan200/computercraft/shared/util/RecipeUtil.java new file mode 100644 index 000000000..c96405cc1 --- /dev/null +++ b/remappedSrc/dan200/computercraft/shared/util/RecipeUtil.java @@ -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.shared.util; + +import com.google.common.collect.Maps; +import com.google.common.collect.Sets; +import com.google.gson.*; +import dan200.computercraft.shared.computer.core.ComputerFamily; +import net.minecraft.recipe.Ingredient; +import net.minecraft.util.JsonHelper; +import net.minecraft.util.collection.DefaultedList; +import java.util.Map; +import java.util.Set; + +public final class RecipeUtil +{ + private RecipeUtil() {} + + public static class ShapedTemplate + { + public final int width; + public final int height; + public final DefaultedList ingredients; + + public ShapedTemplate( int width, int height, DefaultedList ingredients ) + { + this.width = width; + this.height = height; + this.ingredients = ingredients; + } + } + + public static ShapedTemplate getTemplate( JsonObject json ) + { + Map ingMap = Maps.newHashMap(); + for( Map.Entry entry : JsonHelper.getObject( json, "key" ).entrySet() ) + { + if( entry.getKey().length() != 1 ) + { + throw new JsonSyntaxException( "Invalid key entry: '" + entry.getKey() + "' is an invalid symbol (must be 1 character only)." ); + } + if( " ".equals( entry.getKey() ) ) + { + throw new JsonSyntaxException( "Invalid key entry: ' ' is a reserved symbol." ); + } + + ingMap.put( entry.getKey().charAt( 0 ), Ingredient.fromJson( entry.getValue() ) ); + } + + ingMap.put( ' ', Ingredient.EMPTY ); + + JsonArray patternJ = JsonHelper.getArray( json, "pattern" ); + + if( patternJ.size() == 0 ) + { + throw new JsonSyntaxException( "Invalid pattern: empty pattern not allowed" ); + } + + String[] pattern = new String[patternJ.size()]; + for( int x = 0; x < pattern.length; x++ ) + { + String line = JsonHelper.asString( patternJ.get( x ), "pattern[" + x + "]" ); + if( x > 0 && pattern[0].length() != line.length() ) + { + throw new JsonSyntaxException( "Invalid pattern: each row must be the same width" ); + } + pattern[x] = line; + } + + int width = pattern[0].length(); + int height = pattern.length; + DefaultedList ingredients = DefaultedList.ofSize( width * height, Ingredient.EMPTY ); + + Set missingKeys = Sets.newHashSet( ingMap.keySet() ); + missingKeys.remove( ' ' ); + + int i = 0; + for( String line : pattern ) + { + for( char chr : line.toCharArray() ) + { + Ingredient ing = ingMap.get( chr ); + if( ing == null ) + { + throw new JsonSyntaxException( "Pattern references symbol '" + chr + "' but it's not defined in the key" ); + } + ingredients.set( i++, ing ); + missingKeys.remove( chr ); + } + } + + if( !missingKeys.isEmpty() ) + { + throw new JsonSyntaxException( "Key defines symbols that aren't used in pattern: " + missingKeys ); + } + + return new ShapedTemplate( width, height, ingredients ); + } + + public static DefaultedList getIngredients( JsonObject json ) + { + DefaultedList ingredients = DefaultedList.of(); + for( JsonElement ele : JsonHelper.getArray( json, "ingredients" ) ) + { + ingredients.add( Ingredient.fromJson( ele ) ); + } + + if( ingredients.isEmpty() ) throw new JsonParseException( "No ingredients for recipe" ); + return ingredients; + } + + public static ComputerFamily getFamily( JsonObject json, String name ) + { + String familyName = JsonHelper.getString( json, name ); + try + { + return ComputerFamily.valueOf( familyName ); + } + catch( IllegalArgumentException e ) + { + throw new JsonSyntaxException( "Unknown computer family '" + familyName + "' for field " + name ); + } + } +} diff --git a/remappedSrc/dan200/computercraft/shared/util/RecordUtil.java b/remappedSrc/dan200/computercraft/shared/util/RecordUtil.java new file mode 100644 index 000000000..7853fd70b --- /dev/null +++ b/remappedSrc/dan200/computercraft/shared/util/RecordUtil.java @@ -0,0 +1,40 @@ +/* + * 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.shared.util; + +import dan200.computercraft.shared.network.NetworkHandler; +import dan200.computercraft.shared.network.NetworkMessage; +import dan200.computercraft.shared.network.client.PlayRecordClientMessage; +import net.minecraft.item.Item; +import net.minecraft.item.ItemStack; +import net.minecraft.item.MusicDiscItem; +import net.minecraft.sound.SoundEvent; +import net.minecraft.text.TranslatableText; +import net.minecraft.util.math.BlockPos; +import net.minecraft.util.math.Vec3d; +import net.minecraft.world.World; + +import javax.annotation.Nonnull; + +public final class RecordUtil +{ + private RecordUtil() {} + + public static void playRecord( SoundEvent record, String recordInfo, World world, BlockPos pos ) + { + NetworkMessage packet = record != null ? new PlayRecordClientMessage( pos, record, recordInfo ) : new PlayRecordClientMessage( pos ); + NetworkHandler.sendToAllAround( packet, world, new Vec3d( pos ), 64 ); + } + + public static String getRecordInfo( @Nonnull ItemStack recordStack ) + { + Item item = recordStack.getItem(); + if( !(item instanceof MusicDiscItem) ) return null; + + return new TranslatableText( item.getTranslationKey() + ".desc" ).getString(); + } +} diff --git a/remappedSrc/dan200/computercraft/shared/util/RedstoneUtil.java b/remappedSrc/dan200/computercraft/shared/util/RedstoneUtil.java new file mode 100644 index 000000000..bbdfe31f8 --- /dev/null +++ b/remappedSrc/dan200/computercraft/shared/util/RedstoneUtil.java @@ -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.shared.util; + +import net.minecraft.block.BlockState; +import net.minecraft.util.math.BlockPos; +import net.minecraft.util.math.Direction; +import net.minecraft.world.World; + +public final class RedstoneUtil +{ + public static void propagateRedstoneOutput( World world, BlockPos pos, Direction side ) + { + // Propagate ordinary output. See BlockRedstoneDiode.notifyNeighbors + BlockState block = world.getBlockState( pos ); + BlockPos neighbourPos = pos.offset( side ); + world.updateNeighbor( neighbourPos, block.getBlock(), pos ); + world.updateNeighborsExcept( neighbourPos, block.getBlock(), side.getOpposite() ); + } +} diff --git a/remappedSrc/dan200/computercraft/shared/util/StringUtil.java b/remappedSrc/dan200/computercraft/shared/util/StringUtil.java new file mode 100644 index 000000000..d8ebcd0e3 --- /dev/null +++ b/remappedSrc/dan200/computercraft/shared/util/StringUtil.java @@ -0,0 +1,47 @@ +/* + * 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.shared.util; + +public final class StringUtil +{ + private StringUtil() {} + + public static String normaliseLabel( String label ) + { + if( label == null ) return null; + + int length = Math.min( 32, label.length() ); + StringBuilder builder = new StringBuilder( length ); + for( int i = 0; i < length; i++ ) + { + char c = label.charAt( i ); + if( (c >= ' ' && c <= '~') || (c >= 161 && c <= 172) || (c >= 174 && c <= 255) ) + { + builder.append( c ); + } + else + { + builder.append( '?' ); + } + } + + return builder.toString(); + } + + public static byte[] encodeString( String string ) + { + byte[] chars = new byte[string.length()]; + + for( int i = 0; i < chars.length; i++ ) + { + char c = string.charAt( i ); + chars[i] = c < 256 ? (byte) c : 63; + } + + return chars; + } +} diff --git a/remappedSrc/dan200/computercraft/shared/util/ThreadUtils.java b/remappedSrc/dan200/computercraft/shared/util/ThreadUtils.java new file mode 100644 index 000000000..1d58cc5a2 --- /dev/null +++ b/remappedSrc/dan200/computercraft/shared/util/ThreadUtils.java @@ -0,0 +1,80 @@ +/* + * 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.shared.util; + +import com.google.common.util.concurrent.ThreadFactoryBuilder; +import dan200.computercraft.ComputerCraft; + +import java.util.concurrent.ThreadFactory; + +/** + * Provides some utilities to create thread groups + */ +public final class ThreadUtils +{ + private static final ThreadGroup baseGroup = new ThreadGroup( "ComputerCraft" ); + + private ThreadUtils() + { + } + + /** + * Get the base thread group, that all off-thread ComputerCraft activities are run on. + * + * @return The ComputerCraft group. + */ + public static ThreadGroup group() + { + return baseGroup; + } + + /** + * Construct a group under ComputerCraft's shared group + * + * @param name The group's name. This will be prefixed with "ComputerCraft-". + * @return The constructed thread group. + */ + public static ThreadGroup group( String name ) + { + return new ThreadGroup( baseGroup, baseGroup.getName() + "-" + name ); + } + + /** + * Create a new {@link ThreadFactoryBuilder}, which constructs threads under a group of the given {@code name}. + * + * Each thread will be of the format {@code ComputerCraft--}, and belong to a group + * called {@code ComputerCraft-} (which in turn will be a child group of the main {@code ComputerCraft} group. + * + * @param name The name for the thread group and child threads. + * @return The constructed thread factory builder, which may be extended with other properties. + * @see #factory(String) + */ + public static ThreadFactoryBuilder builder( String name ) + { + ThreadGroup group = group( name ); + return new ThreadFactoryBuilder() + .setDaemon( true ) + .setNameFormat( group.getName().replace( "%", "%%" ) + "-%d" ) + .setUncaughtExceptionHandler( ( t, e ) -> ComputerCraft.log.error( "Exception in thread " + t.getName(), e ) ) + .setThreadFactory( x -> new Thread( group, x ) ); + } + + /** + * Create a new {@link ThreadFactory}, which constructs threads under a group of the given {@code name}. + * + * Each thread will be of the format {@code ComputerCraft--}, and belong to a group + * called {@code ComputerCraft-} (which in turn will be a child group of the main {@code ComputerCraft} group. + * + * @param name The name for the thread group and child threads. + * @return The constructed thread factory. + * @see #builder(String) + */ + public static ThreadFactory factory( String name ) + { + return builder( name ).build(); + } +} diff --git a/remappedSrc/dan200/computercraft/shared/util/TickScheduler.java b/remappedSrc/dan200/computercraft/shared/util/TickScheduler.java new file mode 100644 index 000000000..4db62d631 --- /dev/null +++ b/remappedSrc/dan200/computercraft/shared/util/TickScheduler.java @@ -0,0 +1,59 @@ +/* + * 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.shared.util; + +import com.google.common.collect.MapMaker; +import dan200.computercraft.shared.common.TileGeneric; +import net.minecraft.block.entity.BlockEntity; +import net.minecraft.util.math.BlockPos; +import net.minecraft.world.World; + +import java.util.Collections; +import java.util.Iterator; +import java.util.Set; + +/** + * A thread-safe version of {@link net.minecraft.world.TickScheduler#schedule(BlockPos, Object, int)}. + * + * We use this when modems and other peripherals change a block in a different thread. + */ +public final class TickScheduler +{ + private TickScheduler() + { + } + + private static final Set toTick = Collections.newSetFromMap( + new MapMaker() + .weakKeys() + .makeMap() + ); + + public static void schedule( TileGeneric tile ) + { + World world = tile.getWorld(); + if( world != null && !world.isClient ) toTick.add( tile ); + } + + public static void tick() + { + Iterator iterator = toTick.iterator(); + while( iterator.hasNext() ) + { + BlockEntity tile = iterator.next(); + iterator.remove(); + + World world = tile.getWorld(); + BlockPos pos = tile.getPos(); + + if( world != null && pos != null && world.isBlockLoaded( pos ) && world.getBlockEntity( pos ) == tile ) + { + world.getBlockTickScheduler().schedule( pos, tile.getCachedState().getBlock(), 0 ); + } + } + } +} diff --git a/remappedSrc/dan200/computercraft/shared/util/ValidatingSlot.java b/remappedSrc/dan200/computercraft/shared/util/ValidatingSlot.java new file mode 100644 index 000000000..12ed97dba --- /dev/null +++ b/remappedSrc/dan200/computercraft/shared/util/ValidatingSlot.java @@ -0,0 +1,28 @@ +/* + * 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.shared.util; + +import net.minecraft.inventory.Inventory; +import net.minecraft.item.ItemStack; +import net.minecraft.screen.slot.Slot; + +public class ValidatingSlot extends Slot +{ + private final int invSlot; + + public ValidatingSlot( Inventory inventoryIn, int index, int xPosition, int yPosition ) + { + super( inventoryIn, index, xPosition, yPosition ); + this.invSlot = index; + } + + @Override + public boolean canInsert( ItemStack stack ) + { + return inventory.isValid( invSlot, stack ); + } +} diff --git a/remappedSrc/dan200/computercraft/shared/util/WaterloggableBlock.java b/remappedSrc/dan200/computercraft/shared/util/WaterloggableBlock.java new file mode 100644 index 000000000..13907526f --- /dev/null +++ b/remappedSrc/dan200/computercraft/shared/util/WaterloggableBlock.java @@ -0,0 +1,59 @@ +/* + * 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.shared.util; + +import net.minecraft.block.BlockState; +import net.minecraft.block.Waterloggable; +import net.minecraft.fluid.FluidState; +import net.minecraft.fluid.Fluids; +import net.minecraft.item.ItemPlacementContext; +import net.minecraft.state.property.BooleanProperty; +import net.minecraft.state.property.Properties; +import net.minecraft.util.math.BlockPos; +import net.minecraft.util.math.Direction; +import net.minecraft.world.WorldAccess; + +/** + * Represents a block which can be filled with water + * + * I'm fairly sure this exists on 1.14, but it's a useful convenience wrapper to have on 1.13. + */ +public interface WaterloggableBlock extends Waterloggable +{ + BooleanProperty WATERLOGGED = Properties.WATERLOGGED; + + /** + * Call from {@link net.minecraft.block.Block#getFluidState(BlockState)} + * + * @param state The current state + * @return This waterlogged block's current fluid + */ + default FluidState getWaterloggedFluidState( BlockState state ) + { + return state.get( WATERLOGGED ) ? Fluids.WATER.getStill( false ) : Fluids.EMPTY.getDefaultState(); + } + + /** + * Call from {@link net.minecraft.block.Block#getStateForNeighborUpdate(BlockState, Direction, BlockState, IWorld, BlockPos, BlockPos)} + * + * @param state The current state + * @param world The position of this block + * @param pos The world this block exists in + */ + default void updateWaterloggedPostPlacement( BlockState state, WorldAccess world, BlockPos pos ) + { + if( state.get( WATERLOGGED ) ) + { + world.getFluidTickScheduler().schedule( pos, Fluids.WATER, Fluids.WATER.getTickRate( world ) ); + } + } + + default boolean getWaterloggedStateForPlacement( ItemPlacementContext context ) + { + return context.getWorld().getFluidState( context.getBlockPos() ).getFluid() == Fluids.WATER; + } +} diff --git a/remappedSrc/dan200/computercraft/shared/util/WorldUtil.java b/remappedSrc/dan200/computercraft/shared/util/WorldUtil.java new file mode 100644 index 000000000..d08db61df --- /dev/null +++ b/remappedSrc/dan200/computercraft/shared/util/WorldUtil.java @@ -0,0 +1,186 @@ +/* + * 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.shared.util; + +import com.google.common.base.Predicate; +import net.minecraft.block.BlockState; +import net.minecraft.entity.*; +import net.minecraft.entity.player.PlayerEntity; +import net.minecraft.item.ItemStack; +import net.minecraft.util.hit.HitResult; +import net.minecraft.util.math.BlockPos; +import net.minecraft.util.math.Box; +import net.minecraft.util.math.Direction; +import net.minecraft.util.math.Vec3d; +import net.minecraft.util.shape.VoxelShape; +import net.minecraft.world.RayTraceContext; +import net.minecraft.world.World; +import org.apache.commons.lang3.tuple.Pair; + +import javax.annotation.Nonnull; +import java.util.List; + +public final class WorldUtil +{ + @SuppressWarnings( "Guava" ) + private static final Predicate CAN_COLLIDE = x -> x != null && x.isAlive() && x.collides(); + + private static final Entity ENTITY = new ItemEntity( EntityType.ITEM, null ) + { + @Override + public EntityDimensions getDimensions( EntityPose pos ) + { + return EntityDimensions.fixed( 0, 0 ); + } + }; + + static + { + ENTITY.noClip = true; + ENTITY.calculateDimensions(); + } + + public static boolean isLiquidBlock( World world, BlockPos pos ) + { + if( !World.isValid( pos ) ) return false; + BlockState state = world.getBlockState( pos ); + return !state.getFluidState().isEmpty(); + } + + public static boolean isVecInside( VoxelShape shape, Vec3d vec ) + { + if( shape.isEmpty() ) return false; + // AxisAlignedBB.contains, but without strict inequalities. + Box bb = shape.getBoundingBox(); + return vec.x >= bb.minX && vec.x <= bb.maxX && vec.y >= bb.minY && vec.y <= bb.maxY && vec.z >= bb.minZ && vec.z <= bb.maxZ; + } + + public static Pair rayTraceEntities( World world, Vec3d vecStart, Vec3d vecDir, double distance ) + { + Vec3d vecEnd = vecStart.add( vecDir.x * distance, vecDir.y * distance, vecDir.z * distance ); + + // Raycast for blocks + ENTITY.updatePosition( vecStart.x, vecStart.y, vecStart.z ); + HitResult result = world.rayTrace( new RayTraceContext( vecStart, vecEnd, RayTraceContext.ShapeType.OUTLINE, RayTraceContext.FluidHandling.NONE, ENTITY ) ); + if( result != null && result.getType() == HitResult.Type.BLOCK ) + { + distance = vecStart.distanceTo( result.getPos() ); + vecEnd = vecStart.add( vecDir.x * distance, vecDir.y * distance, vecDir.z * distance ); + } + + // Check for entities + float xStretch = Math.abs( vecDir.x ) > 0.25f ? 0.0f : 1.0f; + float yStretch = Math.abs( vecDir.y ) > 0.25f ? 0.0f : 1.0f; + float zStretch = Math.abs( vecDir.z ) > 0.25f ? 0.0f : 1.0f; + Box bigBox = new Box( + Math.min( vecStart.x, vecEnd.x ) - 0.375f * xStretch, + Math.min( vecStart.y, vecEnd.y ) - 0.375f * yStretch, + Math.min( vecStart.z, vecEnd.z ) - 0.375f * zStretch, + Math.max( vecStart.x, vecEnd.x ) + 0.375f * xStretch, + Math.max( vecStart.y, vecEnd.y ) + 0.375f * yStretch, + Math.max( vecStart.z, vecEnd.z ) + 0.375f * zStretch + ); + + Entity closest = null; + double closestDist = 99.0; + List list = world.getEntities( Entity.class, bigBox, CAN_COLLIDE ); + for( Entity entity : list ) + { + Box littleBox = entity.getBoundingBox(); + + if( littleBox.contains( vecStart ) ) + { + closest = entity; + closestDist = 0.0f; + continue; + } + + Vec3d littleBoxResult = littleBox.rayTrace( vecStart, vecEnd ).orElse( null ); + if( littleBoxResult != null ) + { + double dist = vecStart.distanceTo( littleBoxResult ); + if( closest == null || dist <= closestDist ) + { + closest = entity; + closestDist = dist; + } + } + else if( littleBox.intersects( bigBox ) ) + { + if( closest == null ) + { + closest = entity; + closestDist = distance; + } + } + } + if( closest != null && closestDist <= distance ) + { + Vec3d closestPos = vecStart.add( vecDir.x * closestDist, vecDir.y * closestDist, vecDir.z * closestDist ); + return Pair.of( closest, closestPos ); + } + return null; + } + + public static Vec3d getRayStart( LivingEntity entity ) + { + return entity.getCameraPosVec( 1 ); + } + + public static Vec3d getRayEnd( PlayerEntity player ) + { + double reach = 5; // TODO: player.getAttributeInstance( PlayerEntity.REACH_DISTANCE ).getAttributeValue(); + Vec3d look = player.getRotationVec( 1 ); + return getRayStart( player ).add( look.x * reach, look.y * reach, look.z * reach ); + } + + public static void dropItemStack( @Nonnull ItemStack stack, World world, BlockPos pos ) + { + dropItemStack( stack, world, pos, null ); + } + + public static void dropItemStack( @Nonnull ItemStack stack, World world, BlockPos pos, Direction direction ) + { + double xDir; + double yDir; + double zDir; + if( direction != null ) + { + xDir = direction.getOffsetX(); + yDir = direction.getOffsetY(); + zDir = direction.getOffsetZ(); + } + else + { + xDir = 0.0; + yDir = 0.0; + zDir = 0.0; + } + + double xPos = pos.getX() + 0.5 + xDir * 0.4; + double yPos = pos.getY() + 0.5 + yDir * 0.4; + double zPos = pos.getZ() + 0.5 + zDir * 0.4; + dropItemStack( stack, world, xPos, yPos, zPos, xDir, yDir, zDir ); + } + + public static void dropItemStack( @Nonnull ItemStack stack, World world, double xPos, double yPos, double zPos ) + { + dropItemStack( stack, world, xPos, yPos, zPos, 0.0, 0.0, 0.0 ); + } + + public static void dropItemStack( @Nonnull ItemStack stack, World world, double xPos, double yPos, double zPos, double xDir, double yDir, double zDir ) + { + ItemEntity item = new ItemEntity( world, xPos, yPos, zPos, stack.copy() ); + item.setVelocity( + xDir * 0.7 + world.getRandom().nextFloat() * 0.2 - 0.1, + yDir * 0.7 + world.getRandom().nextFloat() * 0.2 - 0.1, + zDir * 0.7 + world.getRandom().nextFloat() * 0.2 - 0.1 + ); + item.resetPickupDelay(); + world.spawnEntity( item ); + } +} diff --git a/remappedSrc/dan200/computercraft/shared/wired/CapabilityWiredElement.java b/remappedSrc/dan200/computercraft/shared/wired/CapabilityWiredElement.java new file mode 100644 index 000000000..78041f683 --- /dev/null +++ b/remappedSrc/dan200/computercraft/shared/wired/CapabilityWiredElement.java @@ -0,0 +1,76 @@ +/* + * 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.shared.wired; + +public final class CapabilityWiredElement +{ + /* + @CapabilityInject( IWiredElement.class ) + public static Capability CAPABILITY = null; + + private CapabilityWiredElement() {} + + public static void register() + { + CapabilityManager.INSTANCE.register( IWiredElement.class, new NullStorage(), NullElement::new ); + } + + private static class NullElement implements IWiredElement + { + @Nonnull + @Override + public IWiredNode getNode() + { + throw new IllegalStateException( "Should not use the default element implementation" ); + } + + @Nonnull + @Override + public World getWorld() + { + throw new IllegalStateException( "Should not use the default element implementation" ); + } + + @Nonnull + @Override + public Vec3d getPosition() + { + throw new IllegalStateException( "Should not use the default element implementation" ); + } + + @Nonnull + @Override + public String getSenderID() + { + throw new IllegalStateException( "Should not use the default element implementation" ); + } + } + + private static class NullStorage implements Capability.IStorage + { + @Override + public INBTBase writeNBT( Capability capability, IWiredElement instance, Direction side ) + { + return null; + } + + @Override + public void readNBT( Capability capability, IWiredElement instance, Direction side, INBTBase base ) + { + } + } + + private static final IWiredElement NULL_ELEMENT = new NullElement(); + + @Nullable + public static IWiredElement unwrap( LazyOptional capability ) + { + IWiredElement element = capability.orElse( NULL_ELEMENT ); + return element == NULL_ELEMENT ? null : element; + } + */ +} diff --git a/remappedSrc/dan200/computercraft/shared/wired/InvariantChecker.java b/remappedSrc/dan200/computercraft/shared/wired/InvariantChecker.java new file mode 100644 index 000000000..128a5ca25 --- /dev/null +++ b/remappedSrc/dan200/computercraft/shared/wired/InvariantChecker.java @@ -0,0 +1,54 @@ +/* + * 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.shared.wired; + +import dan200.computercraft.ComputerCraft; + +/** + * Verifies certain elements of a network are "well formed". + * + * This adds substantial overhead to network modification, and so should only be enabled + * in a development environment. + */ +public final class InvariantChecker +{ + private static final boolean ENABLED = false; + + private InvariantChecker() {} + + public static void checkNode( WiredNode node ) + { + if( !ENABLED ) return; + + WiredNetwork network = node.network; + if( network == null ) + { + ComputerCraft.log.error( "Node's network is null", new Exception() ); + return; + } + + if( network.nodes == null || !network.nodes.contains( node ) ) + { + ComputerCraft.log.error( "Node's network does not contain node", new Exception() ); + } + + for( WiredNode neighbour : node.neighbours ) + { + if( !neighbour.neighbours.contains( node ) ) + { + ComputerCraft.log.error( "Neighbour is missing node", new Exception() ); + } + } + } + + public static void checkNetwork( WiredNetwork network ) + { + if( !ENABLED ) return; + + for( WiredNode node : network.nodes ) checkNode( node ); + } +} diff --git a/remappedSrc/dan200/computercraft/shared/wired/WiredNetwork.java b/remappedSrc/dan200/computercraft/shared/wired/WiredNetwork.java new file mode 100644 index 000000000..8653f0ccd --- /dev/null +++ b/remappedSrc/dan200/computercraft/shared/wired/WiredNetwork.java @@ -0,0 +1,465 @@ +/* + * 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.shared.wired; + +import com.google.common.collect.ImmutableMap; +import dan200.computercraft.api.network.Packet; +import dan200.computercraft.api.network.wired.IWiredNetwork; +import dan200.computercraft.api.network.wired.IWiredNode; +import dan200.computercraft.api.peripheral.IPeripheral; +import net.minecraft.util.math.Vec3d; +import net.minecraft.world.World; + +import javax.annotation.Nonnull; +import java.util.*; +import java.util.concurrent.locks.ReadWriteLock; +import java.util.concurrent.locks.ReentrantReadWriteLock; + +public final class WiredNetwork implements IWiredNetwork +{ + final ReadWriteLock lock = new ReentrantReadWriteLock(); + HashSet nodes; + private HashMap peripherals = new HashMap<>(); + + WiredNetwork( WiredNode node ) + { + nodes = new HashSet<>( 1 ); + nodes.add( node ); + } + + private WiredNetwork( HashSet nodes ) + { + this.nodes = nodes; + } + + @Override + public boolean connect( @Nonnull IWiredNode nodeU, @Nonnull IWiredNode nodeV ) + { + WiredNode wiredU = checkNode( nodeU ); + WiredNode wiredV = checkNode( nodeV ); + if( nodeU == nodeV ) throw new IllegalArgumentException( "Cannot add a connection to oneself." ); + + lock.writeLock().lock(); + try + { + if( nodes == null ) throw new IllegalStateException( "Cannot add a connection to an empty network." ); + + boolean hasU = wiredU.network == this; + boolean hasV = wiredV.network == this; + if( !hasU && !hasV ) throw new IllegalArgumentException( "Neither node is in the network." ); + + // We're going to assimilate a node. Copy across all edges and vertices. + if( !hasU || !hasV ) + { + WiredNetwork other = hasU ? wiredV.network : wiredU.network; + other.lock.writeLock().lock(); + try + { + // Cache several properties for iterating over later + Map otherPeripherals = other.peripherals; + Map thisPeripherals = otherPeripherals.isEmpty() ? peripherals : new HashMap<>( peripherals ); + + Collection thisNodes = otherPeripherals.isEmpty() ? nodes : new ArrayList<>( nodes ); + Collection otherNodes = other.nodes; + + // Move all nodes across into this network, destroying the original nodes. + nodes.addAll( otherNodes ); + for( WiredNode node : otherNodes ) node.network = this; + other.nodes = null; + + // Move all peripherals across, + other.peripherals = null; + peripherals.putAll( otherPeripherals ); + + if( !thisPeripherals.isEmpty() ) + { + WiredNetworkChange.added( thisPeripherals ).broadcast( otherNodes ); + } + + if( !otherPeripherals.isEmpty() ) + { + WiredNetworkChange.added( otherPeripherals ).broadcast( thisNodes ); + } + } + finally + { + other.lock.writeLock().unlock(); + } + } + + boolean added = wiredU.neighbours.add( wiredV ); + if( added ) wiredV.neighbours.add( wiredU ); + + InvariantChecker.checkNetwork( this ); + InvariantChecker.checkNode( wiredU ); + InvariantChecker.checkNode( wiredV ); + + return added; + } + finally + { + lock.writeLock().unlock(); + } + } + + @Override + public boolean disconnect( @Nonnull IWiredNode nodeU, @Nonnull IWiredNode nodeV ) + { + WiredNode wiredU = checkNode( nodeU ); + WiredNode wiredV = checkNode( nodeV ); + if( nodeU == nodeV ) throw new IllegalArgumentException( "Cannot remove a connection to oneself." ); + + lock.writeLock().lock(); + try + { + boolean hasU = wiredU.network == this; + boolean hasV = wiredV.network == this; + if( !hasU || !hasV ) throw new IllegalArgumentException( "One node is not in the network." ); + + // If there was no connection to remove then split. + if( !wiredU.neighbours.remove( wiredV ) ) return false; + wiredV.neighbours.remove( wiredU ); + + // Determine if there is still some connection from u to v. + // Note this is an inlining of reachableNodes which short-circuits + // if all nodes are reachable. + Queue enqueued = new ArrayDeque<>(); + HashSet reachableU = new HashSet<>(); + + reachableU.add( wiredU ); + enqueued.add( wiredU ); + + while( !enqueued.isEmpty() ) + { + WiredNode node = enqueued.remove(); + for( WiredNode neighbour : node.neighbours ) + { + // If we can reach wiredV from wiredU then abort. + if( neighbour == wiredV ) return true; + + // Otherwise attempt to enqueue this neighbour as well. + if( reachableU.add( neighbour ) ) enqueued.add( neighbour ); + } + } + + // Create a new network with all U-reachable nodes/edges and remove them + // from the existing graph. + WiredNetwork networkU = new WiredNetwork( reachableU ); + networkU.lock.writeLock().lock(); + try + { + // Remove nodes from this network + nodes.removeAll( reachableU ); + + // Set network and transfer peripherals + for( WiredNode node : reachableU ) + { + node.network = networkU; + networkU.peripherals.putAll( node.peripherals ); + peripherals.keySet().removeAll( node.peripherals.keySet() ); + } + + // Broadcast changes + if( !peripherals.isEmpty() ) WiredNetworkChange.removed( peripherals ).broadcast( networkU.nodes ); + if( !networkU.peripherals.isEmpty() ) + { + WiredNetworkChange.removed( networkU.peripherals ).broadcast( nodes ); + } + + InvariantChecker.checkNetwork( this ); + InvariantChecker.checkNetwork( networkU ); + InvariantChecker.checkNode( wiredU ); + InvariantChecker.checkNode( wiredV ); + + return true; + } + finally + { + networkU.lock.writeLock().unlock(); + } + } + finally + { + lock.writeLock().unlock(); + } + } + + @Override + public boolean remove( @Nonnull IWiredNode node ) + { + WiredNode wired = checkNode( node ); + + lock.writeLock().lock(); + try + { + // If we're the empty graph then just abort: nodes must have _some_ network. + if( nodes == null ) return false; + if( nodes.size() <= 1 ) return false; + if( wired.network != this ) return false; + + HashSet neighbours = wired.neighbours; + + // Remove this node and move into a separate network. + nodes.remove( wired ); + for( WiredNode neighbour : neighbours ) neighbour.neighbours.remove( wired ); + + WiredNetwork wiredNetwork = new WiredNetwork( wired ); + + // If we're a leaf node in the graph (only one neighbour) then we don't need to + // check for network splitting + if( neighbours.size() == 1 ) + { + // Broadcast our simple peripheral changes + removeSingleNode( wired, wiredNetwork ); + InvariantChecker.checkNode( wired ); + InvariantChecker.checkNetwork( wiredNetwork ); + return true; + } + + HashSet reachable = reachableNodes( neighbours.iterator().next() ); + + // If all nodes are reachable then exit. + if( reachable.size() == nodes.size() ) + { + // Broadcast our simple peripheral changes + removeSingleNode( wired, wiredNetwork ); + InvariantChecker.checkNode( wired ); + InvariantChecker.checkNetwork( wiredNetwork ); + return true; + } + + // A split may cause 2..neighbours.size() separate networks, so we + // iterate through our neighbour list, generating child networks. + neighbours.removeAll( reachable ); + ArrayList maximals = new ArrayList<>( neighbours.size() + 1 ); + maximals.add( wiredNetwork ); + maximals.add( new WiredNetwork( reachable ) ); + + while( !neighbours.isEmpty() ) + { + reachable = reachableNodes( neighbours.iterator().next() ); + neighbours.removeAll( reachable ); + maximals.add( new WiredNetwork( reachable ) ); + } + + for( WiredNetwork network : maximals ) network.lock.writeLock().lock(); + + try + { + // We special case the original node: detaching all peripherals when needed. + wired.network = wiredNetwork; + wired.peripherals = Collections.emptyMap(); + + // Ensure every network is finalised + for( WiredNetwork network : maximals ) + { + for( WiredNode child : network.nodes ) + { + child.network = network; + network.peripherals.putAll( child.peripherals ); + } + } + + for( WiredNetwork network : maximals ) InvariantChecker.checkNetwork( network ); + InvariantChecker.checkNode( wired ); + + // Then broadcast network changes once all nodes are finalised + for( WiredNetwork network : maximals ) + { + WiredNetworkChange.changeOf( peripherals, network.peripherals ).broadcast( network.nodes ); + } + } + finally + { + for( WiredNetwork network : maximals ) network.lock.writeLock().unlock(); + } + + nodes.clear(); + peripherals.clear(); + + return true; + } + finally + { + lock.writeLock().unlock(); + } + } + + @Override + public void updatePeripherals( @Nonnull IWiredNode node, @Nonnull Map newPeripherals ) + { + WiredNode wired = checkNode( node ); + Objects.requireNonNull( peripherals, "peripherals cannot be null" ); + + lock.writeLock().lock(); + try + { + if( wired.network != this ) throw new IllegalStateException( "Node is not on this network" ); + + Map oldPeripherals = wired.peripherals; + WiredNetworkChange change = WiredNetworkChange.changeOf( oldPeripherals, newPeripherals ); + if( change.isEmpty() ) return; + + wired.peripherals = ImmutableMap.copyOf( newPeripherals ); + + // Detach the old peripherals then remove them. + peripherals.keySet().removeAll( change.peripheralsRemoved().keySet() ); + + // Add the new peripherals and attach them + peripherals.putAll( change.peripheralsAdded() ); + + change.broadcast( nodes ); + } + finally + { + lock.writeLock().unlock(); + } + } + + static void transmitPacket( WiredNode start, Packet packet, double range, boolean interdimensional ) + { + Map points = new HashMap<>(); + TreeSet transmitTo = new TreeSet<>(); + + { + TransmitPoint startEntry = start.element.getWorld() != packet.getSender().getWorld() + ? new TransmitPoint( start, Double.POSITIVE_INFINITY, true ) + : new TransmitPoint( start, start.element.getPosition().distanceTo( packet.getSender().getPosition() ), false ); + points.put( start, startEntry ); + transmitTo.add( startEntry ); + } + + { + TransmitPoint point; + while( (point = transmitTo.pollFirst()) != null ) + { + World world = point.node.element.getWorld(); + Vec3d position = point.node.element.getPosition(); + for( WiredNode neighbour : point.node.neighbours ) + { + TransmitPoint neighbourPoint = points.get( neighbour ); + + boolean newInterdimensional; + double newDistance; + if( world != neighbour.element.getWorld() ) + { + newInterdimensional = true; + newDistance = Double.POSITIVE_INFINITY; + } + else + { + newInterdimensional = false; + newDistance = point.distance + position.distanceTo( neighbour.element.getPosition() ); + } + + if( neighbourPoint == null ) + { + TransmitPoint nextPoint = new TransmitPoint( neighbour, newDistance, newInterdimensional ); + points.put( neighbour, nextPoint ); + transmitTo.add( nextPoint ); + } + else if( newDistance < neighbourPoint.distance ) + { + transmitTo.remove( neighbourPoint ); + neighbourPoint.distance = newDistance; + neighbourPoint.interdimensional = newInterdimensional; + transmitTo.add( neighbourPoint ); + } + } + } + } + + for( TransmitPoint point : points.values() ) + { + point.node.tryTransmit( packet, point.distance, point.interdimensional, range, interdimensional ); + } + } + + private void removeSingleNode( WiredNode wired, WiredNetwork wiredNetwork ) + { + wiredNetwork.lock.writeLock().lock(); + try + { + // Cache all the old nodes. + Map wiredPeripherals = new HashMap<>( wired.peripherals ); + + // Setup the new node's network + // Detach the old peripherals then remove them from the old network + wired.network = wiredNetwork; + wired.neighbours.clear(); + wired.peripherals = Collections.emptyMap(); + + // Broadcast the change + if( !peripherals.isEmpty() ) WiredNetworkChange.removed( peripherals ).broadcast( wired ); + + // Now remove all peripherals from this network and broadcast the change. + peripherals.keySet().removeAll( wiredPeripherals.keySet() ); + if( !wiredPeripherals.isEmpty() ) WiredNetworkChange.removed( wiredPeripherals ).broadcast( nodes ); + + } + finally + { + wiredNetwork.lock.writeLock().unlock(); + } + } + + private static class TransmitPoint implements Comparable + { + final WiredNode node; + double distance; + boolean interdimensional; + + TransmitPoint( WiredNode node, double distance, boolean interdimensional ) + { + this.node = node; + this.distance = distance; + this.interdimensional = interdimensional; + } + + @Override + public int compareTo( @Nonnull TransmitPoint o ) + { + // Objects with the same distance are not the same object, so we must add an additional layer of ordering. + return distance == o.distance + ? Integer.compare( node.hashCode(), o.node.hashCode() ) + : Double.compare( distance, o.distance ); + } + } + + private static WiredNode checkNode( IWiredNode node ) + { + if( node instanceof WiredNode ) + { + return (WiredNode) node; + } + else + { + throw new IllegalArgumentException( "Unknown implementation of IWiredNode: " + node ); + } + } + + private static HashSet reachableNodes( WiredNode start ) + { + Queue enqueued = new ArrayDeque<>(); + HashSet reachable = new HashSet<>(); + + reachable.add( start ); + enqueued.add( start ); + + WiredNode node; + while( (node = enqueued.poll()) != null ) + { + for( WiredNode neighbour : node.neighbours ) + { + // Otherwise attempt to enqueue this neighbour as well. + if( reachable.add( neighbour ) ) enqueued.add( neighbour ); + } + } + + return reachable; + } +} diff --git a/remappedSrc/dan200/computercraft/shared/wired/WiredNetworkChange.java b/remappedSrc/dan200/computercraft/shared/wired/WiredNetworkChange.java new file mode 100644 index 000000000..ed287222f --- /dev/null +++ b/remappedSrc/dan200/computercraft/shared/wired/WiredNetworkChange.java @@ -0,0 +1,123 @@ +/* + * 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.shared.wired; + +import dan200.computercraft.api.network.wired.IWiredNetworkChange; +import dan200.computercraft.api.peripheral.IPeripheral; + +import javax.annotation.Nonnull; +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; + +public final class WiredNetworkChange implements IWiredNetworkChange +{ + private static final WiredNetworkChange EMPTY = new WiredNetworkChange( Collections.emptyMap(), Collections.emptyMap() ); + + private final Map removed; + private final Map added; + + private WiredNetworkChange( Map removed, Map added ) + { + this.removed = removed; + this.added = added; + } + + public static WiredNetworkChange changed( Map removed, Map added ) + { + return new WiredNetworkChange( Collections.unmodifiableMap( removed ), Collections.unmodifiableMap( added ) ); + } + + public static WiredNetworkChange added( Map added ) + { + return added.isEmpty() ? EMPTY : new WiredNetworkChange( Collections.emptyMap(), Collections.unmodifiableMap( added ) ); + } + + public static WiredNetworkChange removed( Map removed ) + { + return removed.isEmpty() ? EMPTY : new WiredNetworkChange( Collections.unmodifiableMap( removed ), Collections.emptyMap() ); + } + + public static WiredNetworkChange changeOf( Map oldPeripherals, Map newPeripherals ) + { + // Handle the trivial cases, where all peripherals have been added or removed. + if( oldPeripherals.isEmpty() && newPeripherals.isEmpty() ) + { + return EMPTY; + } + else if( oldPeripherals.isEmpty() ) + { + return new WiredNetworkChange( Collections.emptyMap(), newPeripherals ); + } + else if( newPeripherals.isEmpty() ) + { + return new WiredNetworkChange( oldPeripherals, Collections.emptyMap() ); + } + + Map added = new HashMap<>( newPeripherals ); + Map removed = new HashMap<>(); + + for( Map.Entry entry : oldPeripherals.entrySet() ) + { + String oldKey = entry.getKey(); + IPeripheral oldValue = entry.getValue(); + if( newPeripherals.containsKey( oldKey ) ) + { + IPeripheral rightValue = added.get( oldKey ); + if( oldValue.equals( rightValue ) ) + { + added.remove( oldKey ); + } + else + { + removed.put( oldKey, oldValue ); + } + } + else + { + removed.put( oldKey, oldValue ); + } + } + + return changed( removed, added ); + } + + @Nonnull + @Override + public Map peripheralsAdded() + { + return added; + } + + @Nonnull + @Override + public Map peripheralsRemoved() + { + return removed; + } + + public boolean isEmpty() + { + return added.isEmpty() && removed.isEmpty(); + } + + void broadcast( Iterable nodes ) + { + if( !isEmpty() ) + { + for( WiredNode node : nodes ) node.element.networkChanged( this ); + } + } + + void broadcast( WiredNode node ) + { + if( !isEmpty() ) + { + node.element.networkChanged( this ); + } + } +} diff --git a/remappedSrc/dan200/computercraft/shared/wired/WiredNode.java b/remappedSrc/dan200/computercraft/shared/wired/WiredNode.java new file mode 100644 index 000000000..6169c2058 --- /dev/null +++ b/remappedSrc/dan200/computercraft/shared/wired/WiredNode.java @@ -0,0 +1,153 @@ +/* + * 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.shared.wired; + +import dan200.computercraft.api.network.IPacketReceiver; +import dan200.computercraft.api.network.Packet; +import dan200.computercraft.api.network.wired.IWiredElement; +import dan200.computercraft.api.network.wired.IWiredNetwork; +import dan200.computercraft.api.network.wired.IWiredNode; +import dan200.computercraft.api.network.wired.IWiredSender; +import dan200.computercraft.api.peripheral.IPeripheral; + +import javax.annotation.Nonnull; +import java.util.*; +import java.util.concurrent.locks.Lock; + +public final class WiredNode implements IWiredNode +{ + private Set receivers; + + final IWiredElement element; + Map peripherals = Collections.emptyMap(); + + final HashSet neighbours = new HashSet<>(); + volatile WiredNetwork network; + + public WiredNode( IWiredElement element ) + { + this.element = element; + network = new WiredNetwork( this ); + } + + @Override + public synchronized void addReceiver( @Nonnull IPacketReceiver receiver ) + { + if( receivers == null ) receivers = new HashSet<>(); + receivers.add( receiver ); + } + + @Override + public synchronized void removeReceiver( @Nonnull IPacketReceiver receiver ) + { + if( receivers != null ) receivers.remove( receiver ); + } + + synchronized void tryTransmit( Packet packet, double packetDistance, boolean packetInterdimensional, double range, boolean interdimensional ) + { + if( receivers == null ) return; + + for( IPacketReceiver receiver : receivers ) + { + if( !packetInterdimensional ) + { + double receiveRange = Math.max( range, receiver.getRange() ); // Ensure range is symmetrical + if( interdimensional || receiver.isInterdimensional() || packetDistance < receiveRange ) + { + receiver.receiveSameDimension( packet, packetDistance + element.getPosition().distanceTo( receiver.getPosition() ) ); + } + } + else + { + if( interdimensional || receiver.isInterdimensional() ) + { + receiver.receiveDifferentDimension( packet ); + } + } + } + } + + @Override + public boolean isWireless() + { + return false; + } + + @Override + public void transmitSameDimension( @Nonnull Packet packet, double range ) + { + Objects.requireNonNull( packet, "packet cannot be null" ); + if( !(packet.getSender() instanceof IWiredSender) || ((IWiredSender) packet.getSender()).getNode() != this ) + { + throw new IllegalArgumentException( "Sender is not in the network" ); + } + + acquireReadLock(); + try + { + WiredNetwork.transmitPacket( this, packet, range, false ); + } + finally + { + network.lock.readLock().unlock(); + } + } + + @Override + public void transmitInterdimensional( @Nonnull Packet packet ) + { + Objects.requireNonNull( packet, "packet cannot be null" ); + if( !(packet.getSender() instanceof IWiredSender) || ((IWiredSender) packet.getSender()).getNode() != this ) + { + throw new IllegalArgumentException( "Sender is not in the network" ); + } + + acquireReadLock(); + try + { + WiredNetwork.transmitPacket( this, packet, 0, true ); + } + finally + { + network.lock.readLock().unlock(); + } + } + + @Nonnull + @Override + public IWiredElement getElement() + { + return element; + } + + @Nonnull + @Override + public IWiredNetwork getNetwork() + { + return network; + } + + @Override + public String toString() + { + return "WiredNode{@" + element.getPosition() + " (" + element.getClass().getSimpleName() + ")}"; + } + + private void acquireReadLock() + { + WiredNetwork currentNetwork = network; + while( true ) + { + Lock lock = currentNetwork.lock.readLock(); + lock.lock(); + if( currentNetwork == network ) return; + + + lock.unlock(); + } + } +} diff --git a/src/main/java/dan200/computercraft/api/AbstractTurtleUpgrade.java b/src/main/java/dan200/computercraft/api/AbstractTurtleUpgrade.java index 45f86d961..a1a65fc0d 100644 --- a/src/main/java/dan200/computercraft/api/AbstractTurtleUpgrade.java +++ b/src/main/java/dan200/computercraft/api/AbstractTurtleUpgrade.java @@ -11,8 +11,7 @@ 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.SystemUtil; - +import net.minecraft.util.Util; import javax.annotation.Nonnull; /** @@ -42,7 +41,7 @@ public abstract class AbstractTurtleUpgrade implements ITurtleUpgrade protected AbstractTurtleUpgrade( Identifier id, TurtleUpgradeType type, ItemStack stack ) { - this( id, type, SystemUtil.createTranslationKey( "upgrade", id ) + ".adjective", stack ); + this( id, type, Util.createTranslationKey( "upgrade", id ) + ".adjective", stack ); } protected AbstractTurtleUpgrade( Identifier id, TurtleUpgradeType type, ItemConvertible item ) diff --git a/src/main/java/dan200/computercraft/api/pocket/AbstractPocketUpgrade.java b/src/main/java/dan200/computercraft/api/pocket/AbstractPocketUpgrade.java index a29602a23..eb20ec9e1 100644 --- a/src/main/java/dan200/computercraft/api/pocket/AbstractPocketUpgrade.java +++ b/src/main/java/dan200/computercraft/api/pocket/AbstractPocketUpgrade.java @@ -9,8 +9,7 @@ package dan200.computercraft.api.pocket; import net.minecraft.item.ItemConvertible; import net.minecraft.item.ItemStack; import net.minecraft.util.Identifier; -import net.minecraft.util.SystemUtil; - +import net.minecraft.util.Util; import javax.annotation.Nonnull; /** @@ -38,7 +37,7 @@ public abstract class AbstractPocketUpgrade implements IPocketUpgrade protected AbstractPocketUpgrade( Identifier id, ItemConvertible item ) { - this( id, SystemUtil.createTranslationKey( "upgrade", id ) + ".adjective", new ItemStack( item ) ); + this( id, Util.createTranslationKey( "upgrade", id ) + ".adjective", new ItemStack( item ) ); } @Nonnull diff --git a/src/main/java/dan200/computercraft/api/turtle/event/FakePlayer.java b/src/main/java/dan200/computercraft/api/turtle/event/FakePlayer.java index c9627309b..1f3408854 100644 --- a/src/main/java/dan200/computercraft/api/turtle/event/FakePlayer.java +++ b/src/main/java/dan200/computercraft/api/turtle/event/FakePlayer.java @@ -13,8 +13,6 @@ 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.container.Container; -import net.minecraft.container.NameableContainerProvider; import net.minecraft.entity.Entity; import net.minecraft.entity.damage.DamageSource; import net.minecraft.entity.effect.StatusEffectInstance; @@ -26,18 +24,20 @@ 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.network.packet.RequestCommandCompletionsC2SPacket; -import net.minecraft.server.network.packet.VehicleMoveC2SPacket; 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.DefaultedList; 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; @@ -65,16 +65,16 @@ public class FakePlayer extends ServerPlayerEntity // region Direct networkHandler access @Override - public void method_6000() { } + public void enterCombat() { } @Override - public void method_6044() { } + public void endCombat() { } @Override public void tick() { } @Override - public void method_14226() { } + public void playerTick() { } @Override public void onDeath( DamageSource damage ) { } @@ -102,7 +102,7 @@ public class FakePlayer extends ServerPlayerEntity public void openEditSignScreen( SignBlockEntity tile ) { } @Override - public OptionalInt openContainer( @Nullable NameableContainerProvider container ) + public OptionalInt openHandledScreen( @Nullable NamedScreenHandlerFactory container ) { return OptionalInt.empty(); } @@ -120,25 +120,25 @@ public class FakePlayer extends ServerPlayerEntity public void openCommandBlockScreen( CommandBlockBlockEntity block ) { } @Override - public void onContainerSlotUpdate( Container container, int slot, ItemStack stack ) { } + public void onSlotUpdate( ScreenHandler container, int slot, ItemStack stack ) { } @Override - public void onContainerRegistered( Container container, DefaultedList defaultedList ) { } + public void onHandlerRegistered( ScreenHandler container, DefaultedList defaultedList ) { } @Override - public void onContainerPropertyUpdate( Container container, int key, int value ) { } + public void onPropertyUpdate( ScreenHandler container, int key, int value ) { } @Override - public void closeContainer() { } + public void closeHandledScreen() { } @Override - public void method_14241() { } + public void updateCursorStack() { } @Override - public void addChatMessage( Text textComponent, boolean status ) { } + public void sendMessage( Text textComponent, boolean status ) { } @Override - protected void method_6040() { } + protected void consumeItem() { } @Override public void lookAt( EntityAnchorArgumentType.EntityAnchor anchor, Vec3d vec3d ) { } @@ -147,13 +147,13 @@ public class FakePlayer extends ServerPlayerEntity public void method_14222( EntityAnchorArgumentType.EntityAnchor self, Entity entity, EntityAnchorArgumentType.EntityAnchor target ) { } @Override - protected void method_6020( StatusEffectInstance statusEffectInstance ) { } + protected void onStatusEffectApplied( StatusEffectInstance statusEffectInstance ) { } @Override - protected void method_6009( StatusEffectInstance statusEffectInstance, boolean particles ) { } + protected void onStatusEffectUpgraded( StatusEffectInstance statusEffectInstance, boolean particles ) { } @Override - protected void method_6129( StatusEffectInstance statusEffectInstance ) { } + protected void onStatusEffectRemoved( StatusEffectInstance statusEffectInstance ) { } @Override public void requestTeleport( double x, double y, double z ) { } @@ -162,16 +162,16 @@ public class FakePlayer extends ServerPlayerEntity public void setGameMode( GameMode gameMode ) { } @Override - public void sendChatMessage( Text textComponent, MessageType chatMessageType ) { } + public void sendMessage( Text textComponent, MessageType chatMessageType ) { } @Override - public String getServerBrand() + public String getIp() { return "[Fake Player]"; } @Override - public void method_14255( String url, String hash ) { } + public void sendResourcePackUrl( String url, String hash ) { } @Override public void onStoppedTracking( Entity entity ) { } @@ -254,7 +254,7 @@ public class FakePlayer extends ServerPlayerEntity } @Override - protected void method_10770( ChannelHandlerContext context, Packet packet ) + protected void channelRead0( ChannelHandlerContext context, Packet packet ) { } @@ -274,7 +274,7 @@ public class FakePlayer extends ServerPlayerEntity } @Override - public void setMinCompressedSize( int size ) + public void setCompressionThreshold( int size ) { } diff --git a/src/main/java/dan200/computercraft/client/ClientTableFormatter.java b/src/main/java/dan200/computercraft/client/ClientTableFormatter.java index 0ae9eb33a..1b0c1d55a 100644 --- a/src/main/java/dan200/computercraft/client/ClientTableFormatter.java +++ b/src/main/java/dan200/computercraft/client/ClientTableFormatter.java @@ -13,7 +13,7 @@ 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.TextComponentUtil; +import net.minecraft.client.util.ChatMessages; import net.minecraft.text.Text; import net.minecraft.util.Formatting; import net.minecraft.util.math.MathHelper; @@ -58,7 +58,7 @@ public class ClientTableFormatter implements TableFormatter @Override public int getWidth( Text component ) { - return renderer().getStringWidth( component.asFormattedString() ); + return renderer().getWidth( component.asFormattedString() ); } @Override @@ -69,7 +69,7 @@ public class ClientTableFormatter implements TableFormatter // Trim the text if it goes over the allowed length int maxWidth = MathHelper.floor( chat.getWidth() / chat.getChatScale() ); - List list = TextComponentUtil.wrapLines( component, maxWidth, mc.textRenderer, false, false ); + List list = ChatMessages.breakRenderedChatMessageLines( component, maxWidth, mc.textRenderer, false, false ); if( !list.isEmpty() ) chat.addMessage( list.get( 0 ), id ); } diff --git a/src/main/java/dan200/computercraft/client/gui/FixedWidthFontRenderer.java b/src/main/java/dan200/computercraft/client/gui/FixedWidthFontRenderer.java index 8b7a53a6a..84358d749 100644 --- a/src/main/java/dan200/computercraft/client/gui/FixedWidthFontRenderer.java +++ b/src/main/java/dan200/computercraft/client/gui/FixedWidthFontRenderer.java @@ -100,7 +100,7 @@ public final class FixedWidthFontRenderer { // Draw the quads Tessellator tessellator = Tessellator.getInstance(); - BufferBuilder renderer = tessellator.getBufferBuilder(); + BufferBuilder renderer = tessellator.getBuffer(); renderer.begin( GL11.GL_TRIANGLES, VertexFormats.POSITION_COLOR ); if( leftMarginSize > 0.0 ) { @@ -138,8 +138,8 @@ public final class FixedWidthFontRenderer { // Draw the quads Tessellator tessellator = Tessellator.getInstance(); - BufferBuilder renderer = tessellator.getBufferBuilder(); - renderer.begin( GL11.GL_TRIANGLES, VertexFormats.POSITION_UV_COLOR ); + BufferBuilder renderer = tessellator.getBuffer(); + renderer.begin( GL11.GL_TRIANGLES, VertexFormats.POSITION_TEXTURE_COLOR ); for( int i = 0; i < s.length(); i++ ) { // Switch colour @@ -166,7 +166,7 @@ public final class FixedWidthFontRenderer if( backgroundColour != null ) { // Bind the background texture - m_textureManager.bindTexture( BACKGROUND ); + m_textureManager.bindTextureInner( BACKGROUND ); // Draw the quads drawStringBackgroundPart( x, y, backgroundColour, leftMarginSize, rightMarginSize, greyScale, p ); @@ -194,7 +194,7 @@ public final class FixedWidthFontRenderer public void bindFont() { - m_textureManager.bindTexture( FONT ); + m_textureManager.bindTextureInner( FONT ); GlStateManager.texParameter( GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_WRAP_S, GL11.GL_CLAMP ); } } diff --git a/src/main/java/dan200/computercraft/client/gui/GuiComputer.java b/src/main/java/dan200/computercraft/client/gui/GuiComputer.java index e25455574..5cea47ff0 100644 --- a/src/main/java/dan200/computercraft/client/gui/GuiComputer.java +++ b/src/main/java/dan200/computercraft/client/gui/GuiComputer.java @@ -14,14 +14,14 @@ 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.AbstractContainerScreen; -import net.minecraft.container.Container; +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 extends AbstractContainerScreen +public class GuiComputer extends HandledScreen { 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" ); @@ -67,13 +67,13 @@ public class GuiComputer extends AbstractContainerScreen int termPxWidth = m_termWidth * FixedWidthFontRenderer.FONT_WIDTH; int termPxHeight = m_termHeight * FixedWidthFontRenderer.FONT_HEIGHT; - containerWidth = termPxWidth + 4 + 24; - containerHeight = termPxHeight + 4 + 24; + 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 + left, 2 + 12 + top, termPxWidth, termPxHeight ); + terminalWrapper = new WidgetWrapper( terminal, 2 + 12 + x, 2 + 12 + y, termPxWidth, termPxHeight ); children.add( terminalWrapper ); setFocused( terminalWrapper ); @@ -113,13 +113,13 @@ public class GuiComputer extends AbstractContainerScreen { case Normal: default: - minecraft.getTextureManager().bindTexture( BACKGROUND_NORMAL ); + minecraft.getTextureManager().bindTextureInner( BACKGROUND_NORMAL ); break; case Advanced: - minecraft.getTextureManager().bindTexture( BACKGROUND_ADVANCED ); + minecraft.getTextureManager().bindTextureInner( BACKGROUND_ADVANCED ); break; case Command: - minecraft.getTextureManager().bindTexture( BACKGROUND_COMMAND ); + minecraft.getTextureManager().bindTextureInner( BACKGROUND_COMMAND ); break; } diff --git a/src/main/java/dan200/computercraft/client/gui/GuiDiskDrive.java b/src/main/java/dan200/computercraft/client/gui/GuiDiskDrive.java index d430c66e2..9ba78ead9 100644 --- a/src/main/java/dan200/computercraft/client/gui/GuiDiskDrive.java +++ b/src/main/java/dan200/computercraft/client/gui/GuiDiskDrive.java @@ -9,12 +9,12 @@ 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.AbstractContainerScreen; +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 AbstractContainerScreen +public class GuiDiskDrive extends HandledScreen { private static final Identifier BACKGROUND = new Identifier( "computercraft", "textures/gui/disk_drive.png" ); @@ -27,16 +27,16 @@ public class GuiDiskDrive extends AbstractContainerScreen protected void drawForeground( int par1, int par2 ) { String title = getTitle().asFormattedString(); - font.draw( title, (containerWidth - font.getStringWidth( title )) / 2.0f, 6, 0x404040 ); - font.draw( I18n.translate( "container.inventory" ), 8, (containerHeight - 96) + 2, 0x404040 ); + 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().bindTexture( BACKGROUND ); - blit( left, top, 0, 0, containerWidth, containerHeight ); + minecraft.getTextureManager().bindTextureInner( BACKGROUND ); + blit( x, y, 0, 0, backgroundWidth, backgroundHeight ); } @Override diff --git a/src/main/java/dan200/computercraft/client/gui/GuiPrinter.java b/src/main/java/dan200/computercraft/client/gui/GuiPrinter.java index 78f31b0ce..ea9390d94 100644 --- a/src/main/java/dan200/computercraft/client/gui/GuiPrinter.java +++ b/src/main/java/dan200/computercraft/client/gui/GuiPrinter.java @@ -9,12 +9,12 @@ 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.AbstractContainerScreen; +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 AbstractContainerScreen +public class GuiPrinter extends HandledScreen { private static final Identifier BACKGROUND = new Identifier( "computercraft", "textures/gui/printer.png" ); @@ -27,18 +27,18 @@ public class GuiPrinter extends AbstractContainerScreen protected void drawForeground( int mouseX, int mouseY ) { String title = getTitle().asFormattedString(); - font.draw( title, (containerWidth - font.getStringWidth( title )) / 2.0f, 6, 0x404040 ); - font.draw( I18n.translate( "container.inventory" ), 8, containerHeight - 96 + 2, 0x404040 ); + 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().bindTexture( BACKGROUND ); - blit( left, top, 0, 0, containerWidth, containerHeight ); + minecraft.getTextureManager().bindTextureInner( BACKGROUND ); + blit( x, y, 0, 0, backgroundWidth, backgroundHeight ); - if( container.isPrinting() ) blit( left + 34, top + 21, 176, 0, 25, 45 ); + if( handler.isPrinting() ) blit( x + 34, y + 21, 176, 0, 25, 45 ); } @Override diff --git a/src/main/java/dan200/computercraft/client/gui/GuiPrintout.java b/src/main/java/dan200/computercraft/client/gui/GuiPrintout.java index 7381ad08a..eb6add708 100644 --- a/src/main/java/dan200/computercraft/client/gui/GuiPrintout.java +++ b/src/main/java/dan200/computercraft/client/gui/GuiPrintout.java @@ -10,13 +10,13 @@ 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.AbstractContainerScreen; +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 AbstractContainerScreen +public class GuiPrintout extends HandledScreen { private final boolean m_book; private final int m_pages; @@ -28,7 +28,7 @@ public class GuiPrintout extends AbstractContainerScreen { super( container, player, container.getStack().getName() ); - containerHeight = Y_SIZE; + backgroundHeight = Y_SIZE; String[] text = ItemPrintout.getText( container.getStack() ); m_text = new TextBuffer[text.length]; @@ -91,8 +91,8 @@ public class GuiPrintout extends AbstractContainerScreen GlStateManager.color4f( 1.0f, 1.0f, 1.0f, 1.0f ); GlStateManager.enableDepthTest(); - drawBorder( left, top, blitOffset, m_page, m_pages, m_book ); - drawText( left + X_TEXT_MARGIN, top + Y_TEXT_MARGIN, ItemPrintout.LINES_PER_PAGE * m_page, m_text, m_colours ); + 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 diff --git a/src/main/java/dan200/computercraft/client/gui/GuiTurtle.java b/src/main/java/dan200/computercraft/client/gui/GuiTurtle.java index 25d6c1323..6359d145f 100644 --- a/src/main/java/dan200/computercraft/client/gui/GuiTurtle.java +++ b/src/main/java/dan200/computercraft/client/gui/GuiTurtle.java @@ -14,12 +14,12 @@ 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.AbstractContainerScreen; +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 AbstractContainerScreen +public class GuiTurtle extends HandledScreen { 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" ); @@ -40,8 +40,8 @@ public class GuiTurtle extends AbstractContainerScreen m_family = turtle.getFamily(); m_computer = turtle.getClientComputer(); - containerWidth = 254; - containerHeight = 217; + backgroundWidth = 254; + backgroundHeight = 217; } @Override @@ -59,7 +59,7 @@ public class GuiTurtle extends AbstractContainerScreen ComputerCraft.terminalHeight_turtle, 2, 2, 2, 2 ); - terminalWrapper = new WidgetWrapper( terminal, 2 + 8 + left, 2 + 8 + top, termPxWidth, termPxHeight ); + terminalWrapper = new WidgetWrapper( terminal, 2 + 8 + x, 2 + 8 + y, termPxWidth, termPxHeight ); children.add( terminalWrapper ); setFocused( terminalWrapper ); @@ -90,8 +90,8 @@ public class GuiTurtle extends AbstractContainerScreen GlStateManager.color4f( 1.0F, 1.0F, 1.0F, 1.0F ); int slotX = slot % 4; int slotY = slot / 4; - minecraft.getTextureManager().bindTexture( advanced ? BACKGROUND_ADVANCED : BACKGROUND_NORMAL ); - blit( left + m_container.m_turtleInvStartX - 2 + slotX * 18, top + m_container.m_playerInvStartY - 2 + slotY * 18, 0, 217, 24, 24 ); + 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 ); } } @@ -104,8 +104,8 @@ public class GuiTurtle extends AbstractContainerScreen // Draw border/inventory GlStateManager.color4f( 1.0F, 1.0F, 1.0F, 1.0F ); - minecraft.getTextureManager().bindTexture( advanced ? BACKGROUND_ADVANCED : BACKGROUND_NORMAL ); - blit( left, top, 0, 0, containerWidth, containerHeight ); + minecraft.getTextureManager().bindTextureInner( advanced ? BACKGROUND_ADVANCED : BACKGROUND_NORMAL ); + blit( x, y, 0, 0, backgroundWidth, backgroundHeight ); drawSelectionSlot( advanced ); } diff --git a/src/main/java/dan200/computercraft/client/gui/widgets/WidgetTerminal.java b/src/main/java/dan200/computercraft/client/gui/widgets/WidgetTerminal.java index f5c0f6482..bb82b74aa 100644 --- a/src/main/java/dan200/computercraft/client/gui/widgets/WidgetTerminal.java +++ b/src/main/java/dan200/computercraft/client/gui/widgets/WidgetTerminal.java @@ -398,11 +398,11 @@ public class WidgetTerminal implements Element int width = termWidth * FixedWidthFontRenderer.FONT_WIDTH + leftMargin + rightMargin; int height = termHeight * FixedWidthFontRenderer.FONT_HEIGHT + topMargin + bottomMargin; - minecraft.getTextureManager().bindTexture( BACKGROUND ); + minecraft.getTextureManager().bindTextureInner( BACKGROUND ); Tessellator tesslector = Tessellator.getInstance(); - BufferBuilder buffer = tesslector.getBufferBuilder(); - buffer.begin( GL11.GL_QUADS, VertexFormats.POSITION_UV ); + 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(); diff --git a/src/main/java/dan200/computercraft/client/proxy/ComputerCraftProxyClient.java b/src/main/java/dan200/computercraft/client/proxy/ComputerCraftProxyClient.java index 3c1fa9f68..3b4cfb046 100644 --- a/src/main/java/dan200/computercraft/client/proxy/ComputerCraftProxyClient.java +++ b/src/main/java/dan200/computercraft/client/proxy/ComputerCraftProxyClient.java @@ -27,8 +27,8 @@ 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.container.ArrayPropertyDelegate; -import net.minecraft.inventory.BasicInventory; +import net.minecraft.inventory.SimpleInventory; +import net.minecraft.screen.ArrayPropertyDelegate; public final class ComputerCraftProxyClient { @@ -60,7 +60,7 @@ public final class ComputerCraftProxyClient ContainerType.registerGui( TileEntityContainerType::turtle, ( id, packet, player ) -> { TileTurtle turtle = (TileTurtle) packet.getTileEntity( player ); return new GuiTurtle( turtle, - new ContainerTurtle( id, player.inventory, new BasicInventory( TileTurtle.INVENTORY_SIZE ), new ArrayPropertyDelegate( 1 ) ), + new ContainerTurtle( id, player.inventory, new SimpleInventory( TileTurtle.INVENTORY_SIZE ), new ArrayPropertyDelegate( 1 ) ), player.inventory ); } ); diff --git a/src/main/java/dan200/computercraft/client/render/ItemMapLikeRenderer.java b/src/main/java/dan200/computercraft/client/render/ItemMapLikeRenderer.java index aa690857a..a94aad8a9 100644 --- a/src/main/java/dan200/computercraft/client/render/ItemMapLikeRenderer.java +++ b/src/main/java/dan200/computercraft/client/render/ItemMapLikeRenderer.java @@ -9,7 +9,6 @@ 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.client.render.FirstPersonRenderer; import net.minecraft.entity.player.PlayerEntity; import net.minecraft.item.ItemStack; import net.minecraft.util.Arm; @@ -65,7 +64,7 @@ public abstract class ItemMapLikeRenderer { GlStateManager.pushMatrix(); GlStateManager.rotatef( offset * 10f, 0f, 0f, 1f ); - ((MixedFirstPersonRenderer) minecraft.getFirstPersonRenderer()).renderArmFirstPerson_CC( equipProgress, swingProgress, side ); + ((MixedFirstPersonRenderer) minecraft.getHeldItemRenderer()).renderArmFirstPerson_CC( equipProgress, swingProgress, side ); GlStateManager.popMatrix(); } @@ -98,7 +97,7 @@ public abstract class ItemMapLikeRenderer */ private void renderItemFirstPersonCenter( float pitch, float equipProgress, float swingProgress, ItemStack stack ) { - MixedFirstPersonRenderer renderer = (MixedFirstPersonRenderer) MinecraftClient.getInstance().getFirstPersonRenderer(); + MixedFirstPersonRenderer renderer = (MixedFirstPersonRenderer) MinecraftClient.getInstance().getHeldItemRenderer(); // Setup the appropriate transformations. This is just copied from the // corresponding method in ItemRenderer. diff --git a/src/main/java/dan200/computercraft/client/render/ItemPocketRenderer.java b/src/main/java/dan200/computercraft/client/render/ItemPocketRenderer.java index bd4a2d59a..ad5d83cc2 100644 --- a/src/main/java/dan200/computercraft/client/render/ItemPocketRenderer.java +++ b/src/main/java/dan200/computercraft/client/render/ItemPocketRenderer.java @@ -100,10 +100,10 @@ public final class ItemPocketRenderer extends ItemMapLikeRenderer else { // Otherwise render a plain background - MinecraftClient.getInstance().getTextureManager().bindTexture( BACKGROUND ); + MinecraftClient.getInstance().getTextureManager().bindTextureInner( BACKGROUND ); Tessellator tessellator = Tessellator.getInstance(); - BufferBuilder buffer = tessellator.getBufferBuilder(); + BufferBuilder buffer = tessellator.getBuffer(); Colour black = Colour.Black; buffer.begin( GL11.GL_QUADS, VertexFormats.POSITION ); @@ -119,7 +119,7 @@ public final class ItemPocketRenderer extends ItemMapLikeRenderer private static void renderFrame( ComputerFamily family, int colour, int width, int height ) { - MinecraftClient.getInstance().getTextureManager().bindTexture( colour != -1 + MinecraftClient.getInstance().getTextureManager().bindTextureInner( colour != -1 ? BACKGROUND_COLOUR : family == ComputerFamily.Normal ? BACKGROUND_NORMAL : BACKGROUND_ADVANCED ); @@ -129,8 +129,8 @@ public final class ItemPocketRenderer extends ItemMapLikeRenderer float b = (colour & 0xFF) / 255.0f; Tessellator tessellator = Tessellator.getInstance(); - BufferBuilder buffer = tessellator.getBufferBuilder(); - buffer.begin( GL11.GL_QUADS, VertexFormats.POSITION_UV_COLOR ); + 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 ); @@ -168,7 +168,7 @@ public final class ItemPocketRenderer extends ItemMapLikeRenderer float b = (colour & 0xFF) / 255.0f; Tessellator tessellator = Tessellator.getInstance(); - BufferBuilder buffer = tessellator.getBufferBuilder(); + 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(); diff --git a/src/main/java/dan200/computercraft/client/render/ModelTransformer.java b/src/main/java/dan200/computercraft/client/render/ModelTransformer.java index 653e890fa..dd4987914 100644 --- a/src/main/java/dan200/computercraft/client/render/ModelTransformer.java +++ b/src/main/java/dan200/computercraft/client/render/ModelTransformer.java @@ -35,7 +35,7 @@ public final class ModelTransformer public static void transformQuadsTo( List output, List input, Matrix4f transform ) { - transformQuadsTo( VertexFormats.POSITION_COLOR_UV_NORMAL, output, input, transform ); + transformQuadsTo( VertexFormats.POSITION_COLOR_TEXTURE_LIGHT_NORMAL, output, input, transform ); } public static void transformQuadsTo( VertexFormat format, List output, List input, Matrix4f transform ) diff --git a/src/main/java/dan200/computercraft/client/render/MonitorHighlightRenderer.java b/src/main/java/dan200/computercraft/client/render/MonitorHighlightRenderer.java index ace9571ef..8d98ec476 100644 --- a/src/main/java/dan200/computercraft/client/render/MonitorHighlightRenderer.java +++ b/src/main/java/dan200/computercraft/client/render/MonitorHighlightRenderer.java @@ -64,7 +64,7 @@ public final class MonitorHighlightRenderer GlStateManager.translated( pos.getX() - camera.getPos().getX(), pos.getY() - camera.getPos().getY(), pos.getZ() - camera.getPos().getZ() ); Tessellator tessellator = Tessellator.getInstance(); - BufferBuilder buffer = tessellator.getBufferBuilder(); + BufferBuilder buffer = tessellator.getBuffer(); buffer.begin( GL11.GL_LINES, VertexFormats.POSITION_COLOR ); // I wish I could think of a better way to do this diff --git a/src/main/java/dan200/computercraft/client/render/PrintoutRenderer.java b/src/main/java/dan200/computercraft/client/render/PrintoutRenderer.java index bfae3c80e..ecce1ae4f 100644 --- a/src/main/java/dan200/computercraft/client/render/PrintoutRenderer.java +++ b/src/main/java/dan200/computercraft/client/render/PrintoutRenderer.java @@ -94,11 +94,11 @@ public final class PrintoutRenderer GlStateManager.enableTexture(); GlStateManager.blendFuncSeparate( SourceFactor.SRC_ALPHA, DestFactor.ONE_MINUS_SRC_ALPHA, SourceFactor.ONE, DestFactor.ZERO ); - MinecraftClient.getInstance().getTextureManager().bindTexture( BG ); + MinecraftClient.getInstance().getTextureManager().bindTextureInner( BG ); Tessellator tessellator = Tessellator.getInstance(); - BufferBuilder buffer = tessellator.getBufferBuilder(); - buffer.begin( GL11.GL_QUADS, VertexFormats.POSITION_UV ); + BufferBuilder buffer = tessellator.getBuffer(); + buffer.begin( GL11.GL_QUADS, VertexFormats.POSITION_TEXTURE ); int leftPages = page; int rightPages = pages - page - 1; diff --git a/src/main/java/dan200/computercraft/client/render/TileEntityCableRenderer.java b/src/main/java/dan200/computercraft/client/render/TileEntityCableRenderer.java index 35eb88965..b18696683 100644 --- a/src/main/java/dan200/computercraft/client/render/TileEntityCableRenderer.java +++ b/src/main/java/dan200/computercraft/client/render/TileEntityCableRenderer.java @@ -48,7 +48,7 @@ public class TileEntityCableRenderer extends BlockEntityRenderer MinecraftClient mc = MinecraftClient.getInstance(); - HitResult hit = mc.hitResult; + HitResult hit = mc.crosshairTarget; if( !(hit instanceof BlockHitResult) || !((BlockHitResult) hit).getBlockPos().equals( pos ) ) return; World world = te.getWorld(); @@ -64,7 +64,7 @@ public class TileEntityCableRenderer extends BlockEntityRenderer preRenderDamagedBlocks(); - BufferBuilder buffer = Tessellator.getInstance().getBufferBuilder(); + 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(); diff --git a/src/main/java/dan200/computercraft/client/render/TileEntityMonitorRenderer.java b/src/main/java/dan200/computercraft/client/render/TileEntityMonitorRenderer.java index f13de4bac..11dd78163 100644 --- a/src/main/java/dan200/computercraft/client/render/TileEntityMonitorRenderer.java +++ b/src/main/java/dan200/computercraft/client/render/TileEntityMonitorRenderer.java @@ -87,7 +87,7 @@ public class TileEntityMonitorRenderer extends BlockEntityRenderer // Get renderers MinecraftClient mc = MinecraftClient.getInstance(); Tessellator tessellator = Tessellator.getInstance(); - BufferBuilder renderer = tessellator.getBufferBuilder(); + BufferBuilder renderer = tessellator.getBuffer(); // Get terminal boolean redraw = originTerminal.pollTerminalChanged(); @@ -127,7 +127,7 @@ public class TileEntityMonitorRenderer extends BlockEntityRenderer GlStateManager.scaled( xScale, -yScale, 1.0 ); // Draw background - mc.getTextureManager().bindTexture( FixedWidthFontRenderer.BACKGROUND ); + mc.getTextureManager().bindTextureInner( FixedWidthFontRenderer.BACKGROUND ); if( redraw ) { // Build background display list @@ -244,14 +244,14 @@ public class TileEntityMonitorRenderer extends BlockEntityRenderer else { // Draw a big black quad - mc.getTextureManager().bindTexture( FixedWidthFontRenderer.BACKGROUND ); + 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_UV_COLOR ); + 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(); @@ -270,7 +270,7 @@ public class TileEntityMonitorRenderer extends BlockEntityRenderer GlStateManager.colorMask( false, false, false, false ); try { - mc.getTextureManager().bindTexture( FixedWidthFontRenderer.BACKGROUND ); + 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(); diff --git a/src/main/java/dan200/computercraft/client/render/TileEntityTurtleRenderer.java b/src/main/java/dan200/computercraft/client/render/TileEntityTurtleRenderer.java index dda9db250..0fe21c50c 100644 --- a/src/main/java/dan200/computercraft/client/render/TileEntityTurtleRenderer.java +++ b/src/main/java/dan200/computercraft/client/render/TileEntityTurtleRenderer.java @@ -85,13 +85,13 @@ public class TileEntityTurtleRenderer extends BlockEntityRenderer { // Render the label String label = turtle.createProxy().getLabel(); - if( label != null && renderManager.hitResult != null && renderManager.hitResult instanceof BlockHitResult && turtle.getPos().equals( ((BlockHitResult) renderManager.hitResult).getBlockPos() ) ) + 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.cameraEntity.getYaw(), renderManager.cameraEntity.getPitch(), false + renderManager.camera.getYaw(), renderManager.camera.getPitch(), false ); disableLightmap( false ); } @@ -206,7 +206,7 @@ public class TileEntityTurtleRenderer extends BlockEntityRenderer { Random random = new Random( 0 ); Tessellator tessellator = Tessellator.getInstance(); - renderManager.textureManager.bindTexture( SpriteAtlasTexture.BLOCK_ATLAS_TEX ); + renderManager.textureManager.bindTextureInner( SpriteAtlasTexture.BLOCK_ATLAS_TEX ); renderQuads( tessellator, model.getQuads( state, null, random ), tints ); for( Direction facing : DirectionUtil.FACINGS ) { @@ -216,8 +216,8 @@ public class TileEntityTurtleRenderer extends BlockEntityRenderer private static void renderQuads( Tessellator tessellator, List quads, int[] tints ) { - BufferBuilder buffer = tessellator.getBufferBuilder(); - VertexFormat format = VertexFormats.POSITION_COLOR_UV_NORMAL; + BufferBuilder buffer = tessellator.getBuffer(); + VertexFormat format = VertexFormats.POSITION_COLOR_TEXTURE_LIGHT_NORMAL; buffer.begin( GL11.GL_QUADS, format ); for( BakedQuad quad : quads ) { diff --git a/src/main/java/dan200/computercraft/client/render/TurtleMultiModel.java b/src/main/java/dan200/computercraft/client/render/TurtleMultiModel.java index c10511e30..1a23bfc07 100644 --- a/src/main/java/dan200/computercraft/client/render/TurtleMultiModel.java +++ b/src/main/java/dan200/computercraft/client/render/TurtleMultiModel.java @@ -9,7 +9,7 @@ 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.ModelItemPropertyOverrideList; +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; @@ -97,9 +97,9 @@ public class TurtleMultiModel implements BakedModel } @Override - public boolean hasDepthInGui() + public boolean hasDepth() { - return m_baseModel.hasDepthInGui(); + return m_baseModel.hasDepth(); } @Override @@ -121,8 +121,8 @@ public class TurtleMultiModel implements BakedModel } @Override - public ModelItemPropertyOverrideList getItemPropertyOverrides() + public ModelOverrideList getOverrides() { - return ModelItemPropertyOverrideList.EMPTY; + return ModelOverrideList.EMPTY; } } diff --git a/src/main/java/dan200/computercraft/client/render/TurtleSmartItemModel.java b/src/main/java/dan200/computercraft/client/render/TurtleSmartItemModel.java index b596c2cff..4fbf7e04f 100644 --- a/src/main/java/dan200/computercraft/client/render/TurtleSmartItemModel.java +++ b/src/main/java/dan200/computercraft/client/render/TurtleSmartItemModel.java @@ -18,7 +18,7 @@ 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.ModelItemPropertyOverrideList; +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; @@ -105,7 +105,7 @@ public class TurtleSmartItemModel implements BakedModel private final BakedModel colourModel; private HashMap m_cachedModels; - private ModelItemPropertyOverrideList m_overrides; + private ModelOverrideList m_overrides; public TurtleSmartItemModel( ModelLoader loader, BakedModel familyModel, BakedModel colourModel ) { @@ -113,7 +113,7 @@ public class TurtleSmartItemModel implements BakedModel this.colourModel = colourModel; m_cachedModels = new HashMap<>(); - m_overrides = new ModelItemPropertyOverrideList( loader, null, null, Collections.emptyList() ) + m_overrides = new ModelOverrideList( loader, null, null, Collections.emptyList() ) { @Nonnull @Override @@ -138,7 +138,7 @@ public class TurtleSmartItemModel implements BakedModel @Nonnull @Override - public ModelItemPropertyOverrideList getItemPropertyOverrides() + public ModelOverrideList getOverrides() { return m_overrides; } @@ -187,9 +187,9 @@ public class TurtleSmartItemModel implements BakedModel } @Override - public boolean hasDepthInGui() + public boolean hasDepth() { - return familyModel.hasDepthInGui(); + return familyModel.hasDepth(); } @Override diff --git a/src/main/java/dan200/computercraft/core/filesystem/ResourceMount.java b/src/main/java/dan200/computercraft/core/filesystem/ResourceMount.java index 33f790549..801b3aca7 100644 --- a/src/main/java/dan200/computercraft/core/filesystem/ResourceMount.java +++ b/src/main/java/dan200/computercraft/core/filesystem/ResourceMount.java @@ -11,6 +11,7 @@ import com.google.common.cache.CacheBuilder; import com.google.common.io.ByteStreams; import dan200.computercraft.api.filesystem.IMount; import dan200.computercraft.core.apis.handles.ArrayByteChannel; +import dan200.computercraft.core.filesystem.ResourceMount.Listener; import net.minecraft.resource.ReloadableResourceManager; import net.minecraft.resource.Resource; import net.minecraft.resource.ResourceManager; diff --git a/src/main/java/dan200/computercraft/core/terminal/Terminal.java b/src/main/java/dan200/computercraft/core/terminal/Terminal.java index be5870b8e..46bf64491 100644 --- a/src/main/java/dan200/computercraft/core/terminal/Terminal.java +++ b/src/main/java/dan200/computercraft/core/terminal/Terminal.java @@ -365,17 +365,17 @@ public class Terminal for( int n = 0; n < m_height; n++ ) { m_text[n].fill( ' ' ); - if( nbt.containsKey( "term_text_" + n ) ) + if( nbt.contains( "term_text_" + n ) ) { m_text[n].write( nbt.getString( "term_text_" + n ) ); } m_textColour[n].fill( base16.charAt( m_cursorColour ) ); - if( nbt.containsKey( "term_textColour_" + n ) ) + if( nbt.contains( "term_textColour_" + n ) ) { m_textColour[n].write( nbt.getString( "term_textColour_" + n ) ); } m_backgroundColour[n].fill( base16.charAt( m_cursorBackgroundColour ) ); - if( nbt.containsKey( "term_textBgColour_" + n ) ) + if( nbt.contains( "term_textBgColour_" + n ) ) { m_backgroundColour[n].write( nbt.getString( "term_textBgColour_" + n ) ); } diff --git a/src/main/java/dan200/computercraft/shared/TurtlePermissions.java b/src/main/java/dan200/computercraft/shared/TurtlePermissions.java index 4c2cc36d8..1cb36a1fe 100644 --- a/src/main/java/dan200/computercraft/shared/TurtlePermissions.java +++ b/src/main/java/dan200/computercraft/shared/TurtlePermissions.java @@ -11,6 +11,7 @@ import dan200.computercraft.ComputerCraft; import dan200.computercraft.api.turtle.event.TurtleActionEvent; import net.minecraft.entity.player.PlayerEntity; import net.minecraft.server.MinecraftServer; +import net.minecraft.server.world.ServerWorld; import net.minecraft.util.math.BlockPos; import net.minecraft.world.World; @@ -19,13 +20,21 @@ public final class TurtlePermissions public static boolean isBlockEnterable( World world, BlockPos pos, PlayerEntity player ) { MinecraftServer server = world.getServer(); - return server == null || world.isClient || !server.isSpawnProtected( world, pos, player ); + + if (!world.isClient) + return !server.isSpawnProtected( (ServerWorld) world, pos, player ); + + return server == null || world.isClient; } public static boolean isBlockEditable( World world, BlockPos pos, PlayerEntity player ) { MinecraftServer server = world.getServer(); - return server == null || world.isClient || !server.isSpawnProtected( world, pos, player ); + + if (!world.isClient) + return !server.isSpawnProtected( (ServerWorld) world, pos, player ); + + return server == null || world.isClient; } @Subscribe diff --git a/src/main/java/dan200/computercraft/shared/command/CommandCopy.java b/src/main/java/dan200/computercraft/shared/command/CommandCopy.java index 75c91bcd9..b60dc2689 100644 --- a/src/main/java/dan200/computercraft/shared/command/CommandCopy.java +++ b/src/main/java/dan200/computercraft/shared/command/CommandCopy.java @@ -54,7 +54,7 @@ public final class CommandCopy { LiteralText name = new LiteralText( text ); name.getStyle() - .setClickEvent( new ClickEvent( ClickEvent.Action.RUN_COMMAND, PREFIX + text ) ) + .withClickEvent( new ClickEvent( ClickEvent.Action.RUN_COMMAND, PREFIX + text ) ) .setHoverEvent( new HoverEvent( HoverEvent.Action.SHOW_TEXT, new TranslatableText( "gui.computercraft.tooltip.copy" ) ) ); return name; } diff --git a/src/main/java/dan200/computercraft/shared/command/arguments/ComputersArgumentType.java b/src/main/java/dan200/computercraft/shared/command/arguments/ComputersArgumentType.java index e72618d19..5023b6771 100644 --- a/src/main/java/dan200/computercraft/shared/command/arguments/ComputersArgumentType.java +++ b/src/main/java/dan200/computercraft/shared/command/arguments/ComputersArgumentType.java @@ -17,9 +17,8 @@ import dan200.computercraft.ComputerCraft; import dan200.computercraft.shared.computer.core.ComputerFamily; import dan200.computercraft.shared.computer.core.ServerComputer; import net.minecraft.command.arguments.serialize.ArgumentSerializer; +import net.minecraft.network.PacketByteBuf; import net.minecraft.server.command.ServerCommandSource; -import net.minecraft.util.PacketByteBuf; - import javax.annotation.Nonnull; import java.util.*; import java.util.concurrent.CompletableFuture; diff --git a/src/main/java/dan200/computercraft/shared/command/arguments/RepeatArgumentType.java b/src/main/java/dan200/computercraft/shared/command/arguments/RepeatArgumentType.java index 84110ad16..f64195720 100644 --- a/src/main/java/dan200/computercraft/shared/command/arguments/RepeatArgumentType.java +++ b/src/main/java/dan200/computercraft/shared/command/arguments/RepeatArgumentType.java @@ -17,10 +17,9 @@ import com.mojang.brigadier.suggestion.Suggestions; import com.mojang.brigadier.suggestion.SuggestionsBuilder; import net.minecraft.command.arguments.ArgumentTypes; import net.minecraft.command.arguments.serialize.ArgumentSerializer; +import net.minecraft.network.PacketByteBuf; import net.minecraft.text.LiteralText; import net.minecraft.text.Text; -import net.minecraft.util.PacketByteBuf; - import javax.annotation.Nonnull; import java.util.ArrayList; import java.util.Collection; diff --git a/src/main/java/dan200/computercraft/shared/command/builder/HelpingArgumentBuilder.java b/src/main/java/dan200/computercraft/shared/command/builder/HelpingArgumentBuilder.java index 032e456bc..d473e178a 100644 --- a/src/main/java/dan200/computercraft/shared/command/builder/HelpingArgumentBuilder.java +++ b/src/main/java/dan200/computercraft/shared/command/builder/HelpingArgumentBuilder.java @@ -191,7 +191,7 @@ public final class HelpingArgumentBuilder extends LiteralArgumentBuilder T coloured( T component, Formatting colour ) { - component.getStyle().setColor( colour ); + component.getStyle().withColor( colour ); return component; } @@ -74,8 +74,8 @@ public final class ChatHelpers { Style style = component.getStyle(); - if( style.getColor() == null ) style.setColor( Formatting.YELLOW ); - style.setClickEvent( new ClickEvent( ClickEvent.Action.RUN_COMMAND, command ) ); + if( style.getColor() == null ) style.withColor( Formatting.YELLOW ); + style.withClickEvent( new ClickEvent( ClickEvent.Action.RUN_COMMAND, command ) ); style.setHoverEvent( new HoverEvent( HoverEvent.Action.SHOW_TEXT, toolTip ) ); return component; diff --git a/src/main/java/dan200/computercraft/shared/common/BlockGeneric.java b/src/main/java/dan200/computercraft/shared/common/BlockGeneric.java index 4d95c0a29..b380327f5 100644 --- a/src/main/java/dan200/computercraft/shared/common/BlockGeneric.java +++ b/src/main/java/dan200/computercraft/shared/common/BlockGeneric.java @@ -36,19 +36,19 @@ public abstract class BlockGeneric extends Block implements BlockEntityProvider @Override @Deprecated - public final void onBlockRemoved( @Nonnull BlockState block, @Nonnull World world, @Nonnull BlockPos pos, BlockState replace, boolean bool ) + public final void onStateReplaced( @Nonnull BlockState block, @Nonnull World world, @Nonnull BlockPos pos, BlockState replace, boolean bool ) { if( block.getBlock() == replace.getBlock() ) return; BlockEntity tile = world.getBlockEntity( pos ); - super.onBlockRemoved( block, world, pos, replace, bool ); + super.onStateReplaced( block, world, pos, replace, bool ); world.removeBlockEntity( pos ); if( tile instanceof TileGeneric ) ((TileGeneric) tile).destroy(); } @Override @Deprecated - public final boolean activate( BlockState state, World world, BlockPos pos, PlayerEntity player, Hand hand, BlockHitResult hit ) + public final boolean onUse( BlockState state, World world, BlockPos pos, PlayerEntity player, Hand hand, BlockHitResult hit ) { BlockEntity tile = world.getBlockEntity( pos ); return tile instanceof TileGeneric && ((TileGeneric) tile).onActivate( player, hand, hit ); @@ -65,7 +65,7 @@ public abstract class BlockGeneric extends Block implements BlockEntityProvider @Override @Deprecated - public void onScheduledTick( BlockState state, World world, BlockPos pos, Random rand ) + public void scheduledTick( BlockState state, World world, BlockPos pos, Random rand ) { BlockEntity te = world.getBlockEntity( pos ); if( te instanceof TileGeneric ) ((TileGeneric) te).blockTick(); diff --git a/src/main/java/dan200/computercraft/shared/common/ClientTerminal.java b/src/main/java/dan200/computercraft/shared/common/ClientTerminal.java index fd13493b5..aec075e53 100644 --- a/src/main/java/dan200/computercraft/shared/common/ClientTerminal.java +++ b/src/main/java/dan200/computercraft/shared/common/ClientTerminal.java @@ -50,7 +50,7 @@ public class ClientTerminal implements ITerminal public void readDescription( CompoundTag nbt ) { m_colour = nbt.getBoolean( "colour" ); - if( nbt.containsKey( "terminal" ) ) + if( nbt.contains( "terminal" ) ) { CompoundTag terminal = nbt.getCompound( "terminal" ); resizeTerminal( terminal.getInt( "term_width" ), terminal.getInt( "term_height" ) ); diff --git a/src/main/java/dan200/computercraft/shared/common/ColourableRecipe.java b/src/main/java/dan200/computercraft/shared/common/ColourableRecipe.java index b5257b174..e27bac54f 100644 --- a/src/main/java/dan200/computercraft/shared/common/ColourableRecipe.java +++ b/src/main/java/dan200/computercraft/shared/common/ColourableRecipe.java @@ -32,9 +32,9 @@ public class ColourableRecipe extends SpecialCraftingRecipe { boolean hasColourable = false; boolean hasDye = false; - for( int i = 0; i < inv.getInvSize(); i++ ) + for( int i = 0; i < inv.size(); i++ ) { - ItemStack stack = inv.getInvStack( i ); + ItemStack stack = inv.getStack( i ); if( stack.isEmpty() ) continue; if( stack.getItem() instanceof IColouredItem ) @@ -63,9 +63,9 @@ public class ColourableRecipe extends SpecialCraftingRecipe ColourTracker tracker = new ColourTracker(); - for( int i = 0; i < inv.getInvSize(); i++ ) + for( int i = 0; i < inv.size(); i++ ) { - ItemStack stack = inv.getInvStack( i ); + ItemStack stack = inv.getStack( i ); if( stack.isEmpty() ) continue; diff --git a/src/main/java/dan200/computercraft/shared/common/ContainerHeldItem.java b/src/main/java/dan200/computercraft/shared/common/ContainerHeldItem.java index 4e8534465..b2e6b6baf 100644 --- a/src/main/java/dan200/computercraft/shared/common/ContainerHeldItem.java +++ b/src/main/java/dan200/computercraft/shared/common/ContainerHeldItem.java @@ -7,14 +7,14 @@ package dan200.computercraft.shared.common; import dan200.computercraft.shared.util.InventoryUtil; -import net.minecraft.container.Container; import net.minecraft.entity.player.PlayerEntity; import net.minecraft.item.ItemStack; +import net.minecraft.screen.ScreenHandler; import net.minecraft.util.Hand; import javax.annotation.Nonnull; -public class ContainerHeldItem extends Container +public class ContainerHeldItem extends ScreenHandler { private final ItemStack m_stack; private final Hand m_hand; diff --git a/src/main/java/dan200/computercraft/shared/common/IColouredItem.java b/src/main/java/dan200/computercraft/shared/common/IColouredItem.java index aa3b998b2..bdc1c468c 100644 --- a/src/main/java/dan200/computercraft/shared/common/IColouredItem.java +++ b/src/main/java/dan200/computercraft/shared/common/IColouredItem.java @@ -28,7 +28,7 @@ public interface IColouredItem static int getColourBasic( ItemStack stack ) { CompoundTag tag = stack.getTag(); - return tag != null && tag.containsKey( NBT_COLOUR ) ? tag.getInt( NBT_COLOUR ) : -1; + return tag != null && tag.contains( NBT_COLOUR ) ? tag.getInt( NBT_COLOUR ) : -1; } static void setColourBasic( ItemStack stack, int colour ) diff --git a/src/main/java/dan200/computercraft/shared/computer/apis/CommandAPI.java b/src/main/java/dan200/computercraft/shared/computer/apis/CommandAPI.java index 8c499eb8f..979d12586 100644 --- a/src/main/java/dan200/computercraft/shared/computer/apis/CommandAPI.java +++ b/src/main/java/dan200/computercraft/shared/computer/apis/CommandAPI.java @@ -141,7 +141,7 @@ public class CommandAPI implements ILuaAPI private static Object getPropertyValue( Property property, Comparable value ) { if( value instanceof String || value instanceof Number || value instanceof Boolean ) return value; - return property.getName( value ); + return property.name( value ); } @Override diff --git a/src/main/java/dan200/computercraft/shared/computer/blocks/BlockComputer.java b/src/main/java/dan200/computercraft/shared/computer/blocks/BlockComputer.java index 51b70e424..8b2b60816 100644 --- a/src/main/java/dan200/computercraft/shared/computer/blocks/BlockComputer.java +++ b/src/main/java/dan200/computercraft/shared/computer/blocks/BlockComputer.java @@ -14,7 +14,7 @@ import net.minecraft.block.Block; import net.minecraft.block.BlockState; import net.minecraft.item.ItemPlacementContext; import net.minecraft.item.ItemStack; -import net.minecraft.state.StateFactory; +import net.minecraft.state.StateManager; import net.minecraft.state.property.DirectionProperty; import net.minecraft.state.property.EnumProperty; import net.minecraft.state.property.Properties; @@ -38,7 +38,7 @@ public class BlockComputer extends BlockComputerBase } @Override - protected void appendProperties( StateFactory.Builder builder ) + protected void appendProperties( StateManager.Builder builder ) { builder.add( FACING, STATE ); } diff --git a/src/main/java/dan200/computercraft/shared/computer/blocks/BlockComputerBase.java b/src/main/java/dan200/computercraft/shared/computer/blocks/BlockComputerBase.java index 518bcdfd9..dd7cf49ed 100644 --- a/src/main/java/dan200/computercraft/shared/computer/blocks/BlockComputerBase.java +++ b/src/main/java/dan200/computercraft/shared/computer/blocks/BlockComputerBase.java @@ -18,14 +18,13 @@ import net.minecraft.block.BlockState; import net.minecraft.block.entity.BlockEntity; import net.minecraft.entity.LivingEntity; import net.minecraft.item.ItemStack; +import net.minecraft.loot.context.LootContext; +import net.minecraft.loot.context.LootContextParameters; import net.minecraft.util.Identifier; import net.minecraft.util.math.BlockPos; import net.minecraft.util.math.Direction; import net.minecraft.world.BlockView; import net.minecraft.world.World; -import net.minecraft.world.loot.context.LootContext; -import net.minecraft.world.loot.context.LootContextParameters; - import javax.annotation.Nonnull; import java.util.List; diff --git a/src/main/java/dan200/computercraft/shared/computer/blocks/TileCommandComputer.java b/src/main/java/dan200/computercraft/shared/computer/blocks/TileCommandComputer.java index 60016c174..2a6219d90 100644 --- a/src/main/java/dan200/computercraft/shared/computer/blocks/TileCommandComputer.java +++ b/src/main/java/dan200/computercraft/shared/computer/blocks/TileCommandComputer.java @@ -55,13 +55,13 @@ public class TileCommandComputer extends TileComputer } @Override - public void sendMessage( Text textComponent ) + public void sendSystemMessage( Text textComponent ) { output.put( output.size() + 1, textComponent.getString() ); } @Override - public boolean sendCommandFeedback() + public boolean shouldReceiveFeedback() { return getWorld().getGameRules().getBoolean( GameRules.SEND_COMMAND_FEEDBACK ); } @@ -124,12 +124,12 @@ public class TileCommandComputer extends TileComputer MinecraftServer server = player.getServer(); if( server == null || !server.areCommandBlocksEnabled() ) { - player.addChatMessage( new TranslatableText( "advMode.notEnabled" ), true ); + player.sendMessage( new TranslatableText( "advMode.notEnabled" ), true ); return false; } else if( !player.isCreativeLevelTwoOp() ) { - player.addChatMessage( new TranslatableText( "advMode.notAllowed" ), true ); + player.sendMessage( new TranslatableText( "advMode.notAllowed" ), true ); return false; } else diff --git a/src/main/java/dan200/computercraft/shared/computer/blocks/TileComputerBase.java b/src/main/java/dan200/computercraft/shared/computer/blocks/TileComputerBase.java index 7ee92f093..92420a69e 100644 --- a/src/main/java/dan200/computercraft/shared/computer/blocks/TileComputerBase.java +++ b/src/main/java/dan200/computercraft/shared/computer/blocks/TileComputerBase.java @@ -92,10 +92,10 @@ public abstract class TileComputerBase extends TileGeneric implements IComputerT */ @Override - public void invalidate() + public void markRemoved() { unload(); - super.invalidate(); + super.markRemoved(); } public abstract void openGUI( PlayerEntity player ); @@ -201,8 +201,8 @@ public abstract class TileComputerBase extends TileGeneric implements IComputerT super.fromTag( nbt ); // Load ID, label and power state - m_computerID = nbt.containsKey( NBT_ID ) ? nbt.getInt( NBT_ID ) : -1; - m_label = nbt.containsKey( NBT_LABEL ) ? nbt.getString( NBT_LABEL ) : null; + m_computerID = nbt.contains( NBT_ID ) ? nbt.getInt( NBT_ID ) : -1; + m_label = nbt.contains( NBT_LABEL ) ? nbt.getString( NBT_LABEL ) : null; m_on = m_startOn = nbt.getBoolean( NBT_ON ); } @@ -410,9 +410,9 @@ public abstract class TileComputerBase extends TileGeneric implements IComputerT protected void readDescription( @Nonnull CompoundTag nbt ) { super.readDescription( nbt ); - m_instanceID = nbt.containsKey( NBT_INSTANCE ) ? nbt.getInt( NBT_INSTANCE ) : -1; - m_label = nbt.containsKey( NBT_LABEL ) ? nbt.getString( NBT_LABEL ) : null; - m_computerID = nbt.containsKey( NBT_ID ) ? nbt.getInt( NBT_ID ) : -1; + m_instanceID = nbt.contains( NBT_INSTANCE ) ? nbt.getInt( NBT_INSTANCE ) : -1; + m_label = nbt.contains( NBT_LABEL ) ? nbt.getString( NBT_LABEL ) : null; + m_computerID = nbt.contains( NBT_ID ) ? nbt.getInt( NBT_ID ) : -1; } protected void transferStateFrom( TileComputerBase copy ) diff --git a/src/main/java/dan200/computercraft/shared/computer/core/ServerComputer.java b/src/main/java/dan200/computercraft/shared/computer/core/ServerComputer.java index 31b6d4c42..1ccaed849 100644 --- a/src/main/java/dan200/computercraft/shared/computer/core/ServerComputer.java +++ b/src/main/java/dan200/computercraft/shared/computer/core/ServerComputer.java @@ -23,9 +23,9 @@ import dan200.computercraft.shared.network.client.ComputerDataClientMessage; import dan200.computercraft.shared.network.client.ComputerDeletedClientMessage; import dan200.computercraft.shared.network.client.ComputerTerminalClientMessage; import net.minecraft.SharedConstants; -import net.minecraft.container.Container; import net.minecraft.entity.player.PlayerEntity; import net.minecraft.nbt.CompoundTag; +import net.minecraft.screen.ScreenHandler; import net.minecraft.server.MinecraftServer; import net.minecraft.util.math.BlockPos; import net.minecraft.world.World; @@ -364,7 +364,7 @@ public class ServerComputer extends ServerTerminal implements IComputer, IComput { if( player == null ) return null; - Container container = player.container; + ScreenHandler container = player.currentScreenHandler; if( !(container instanceof IContainerComputer) ) return null; IContainerComputer computerContainer = (IContainerComputer) container; diff --git a/src/main/java/dan200/computercraft/shared/computer/inventory/ContainerComputer.java b/src/main/java/dan200/computercraft/shared/computer/inventory/ContainerComputer.java index 4e945857a..59cd62dba 100644 --- a/src/main/java/dan200/computercraft/shared/computer/inventory/ContainerComputer.java +++ b/src/main/java/dan200/computercraft/shared/computer/inventory/ContainerComputer.java @@ -10,13 +10,12 @@ import dan200.computercraft.shared.computer.blocks.TileComputer; import dan200.computercraft.shared.computer.core.IComputer; import dan200.computercraft.shared.computer.core.IContainerComputer; import dan200.computercraft.shared.computer.core.InputState; -import net.minecraft.container.Container; import net.minecraft.entity.player.PlayerEntity; - +import net.minecraft.screen.ScreenHandler; import javax.annotation.Nonnull; import javax.annotation.Nullable; -public class ContainerComputer extends Container implements IContainerComputer +public class ContainerComputer extends ScreenHandler implements IContainerComputer { private final TileComputer computer; private final InputState input = new InputState( this ); diff --git a/src/main/java/dan200/computercraft/shared/computer/inventory/ContainerViewComputer.java b/src/main/java/dan200/computercraft/shared/computer/inventory/ContainerViewComputer.java index 737dd8673..93ecbe8b4 100644 --- a/src/main/java/dan200/computercraft/shared/computer/inventory/ContainerViewComputer.java +++ b/src/main/java/dan200/computercraft/shared/computer/inventory/ContainerViewComputer.java @@ -8,15 +8,15 @@ package dan200.computercraft.shared.computer.inventory; import dan200.computercraft.ComputerCraft; import dan200.computercraft.shared.computer.core.*; -import net.minecraft.container.Container; import net.minecraft.entity.player.PlayerEntity; +import net.minecraft.screen.ScreenHandler; import net.minecraft.server.MinecraftServer; import net.minecraft.text.TranslatableText; import javax.annotation.Nonnull; import javax.annotation.Nullable; -public class ContainerViewComputer extends Container implements IContainerComputer +public class ContainerViewComputer extends ScreenHandler implements IContainerComputer { private final IComputer computer; private final InputState input = new InputState( this ); @@ -53,12 +53,12 @@ public class ContainerViewComputer extends Container implements IContainerComput MinecraftServer server = player.getServer(); if( server == null || !server.areCommandBlocksEnabled() ) { - player.addChatMessage( new TranslatableText( "advMode.notEnabled" ), false ); + player.sendMessage( new TranslatableText( "advMode.notEnabled" ), false ); return false; } else if( !player.isCreativeLevelTwoOp() ) { - player.addChatMessage( new TranslatableText( "advMode.notAllowed" ), false ); + player.sendMessage( new TranslatableText( "advMode.notAllowed" ), false ); return false; } } diff --git a/src/main/java/dan200/computercraft/shared/computer/items/IComputerItem.java b/src/main/java/dan200/computercraft/shared/computer/items/IComputerItem.java index abf66da00..bf85ad668 100644 --- a/src/main/java/dan200/computercraft/shared/computer/items/IComputerItem.java +++ b/src/main/java/dan200/computercraft/shared/computer/items/IComputerItem.java @@ -19,7 +19,7 @@ public interface IComputerItem default int getComputerID( @Nonnull ItemStack stack ) { CompoundTag nbt = stack.getTag(); - return nbt != null && nbt.containsKey( NBT_ID ) ? nbt.getInt( NBT_ID ) : -1; + return nbt != null && nbt.contains( NBT_ID ) ? nbt.getInt( NBT_ID ) : -1; } default String getLabel( @Nonnull ItemStack stack ) diff --git a/src/main/java/dan200/computercraft/shared/computer/recipe/ComputerConvertRecipe.java b/src/main/java/dan200/computercraft/shared/computer/recipe/ComputerConvertRecipe.java index 8f96e59d4..c5db029be 100644 --- a/src/main/java/dan200/computercraft/shared/computer/recipe/ComputerConvertRecipe.java +++ b/src/main/java/dan200/computercraft/shared/computer/recipe/ComputerConvertRecipe.java @@ -11,8 +11,8 @@ import net.minecraft.inventory.CraftingInventory; import net.minecraft.item.ItemStack; import net.minecraft.recipe.Ingredient; import net.minecraft.recipe.ShapedRecipe; -import net.minecraft.util.DefaultedList; import net.minecraft.util.Identifier; +import net.minecraft.util.collection.DefaultedList; import net.minecraft.world.World; import javax.annotation.Nonnull; @@ -36,11 +36,11 @@ public abstract class ComputerConvertRecipe extends ShapedRecipe @Override public boolean matches( @Nonnull CraftingInventory inventory, @Nonnull World world ) { - if( !method_17728( inventory, world ) ) return false; + if( !matches( inventory, world ) ) return false; - for( int i = 0; i < inventory.getInvSize(); i++ ) + for( int i = 0; i < inventory.size(); i++ ) { - if( inventory.getInvStack( i ).getItem() instanceof IComputerItem ) return true; + if( inventory.getStack( i ).getItem() instanceof IComputerItem ) return true; } return false; @@ -51,9 +51,9 @@ public abstract class ComputerConvertRecipe extends ShapedRecipe public ItemStack craft( @Nonnull CraftingInventory inventory ) { // Find our computer item and convert it. - for( int i = 0; i < inventory.getInvSize(); i++ ) + for( int i = 0; i < inventory.size(); i++ ) { - ItemStack stack = inventory.getInvStack( i ); + ItemStack stack = inventory.getStack( i ); if( stack.getItem() instanceof IComputerItem ) return convert( (IComputerItem) stack.getItem(), stack ); } diff --git a/src/main/java/dan200/computercraft/shared/computer/recipe/ComputerFamilyRecipe.java b/src/main/java/dan200/computercraft/shared/computer/recipe/ComputerFamilyRecipe.java index 081f062d0..6e347d1f5 100644 --- a/src/main/java/dan200/computercraft/shared/computer/recipe/ComputerFamilyRecipe.java +++ b/src/main/java/dan200/computercraft/shared/computer/recipe/ComputerFamilyRecipe.java @@ -10,14 +10,13 @@ import com.google.gson.JsonObject; import dan200.computercraft.shared.computer.core.ComputerFamily; import dan200.computercraft.shared.util.RecipeUtil; import net.minecraft.item.ItemStack; +import net.minecraft.network.PacketByteBuf; import net.minecraft.recipe.Ingredient; import net.minecraft.recipe.RecipeSerializer; import net.minecraft.recipe.ShapedRecipe; -import net.minecraft.util.DefaultedList; import net.minecraft.util.Identifier; import net.minecraft.util.JsonHelper; -import net.minecraft.util.PacketByteBuf; - +import net.minecraft.util.collection.DefaultedList; import javax.annotation.Nonnull; public abstract class ComputerFamilyRecipe extends ComputerConvertRecipe diff --git a/src/main/java/dan200/computercraft/shared/computer/recipe/ComputerUpgradeRecipe.java b/src/main/java/dan200/computercraft/shared/computer/recipe/ComputerUpgradeRecipe.java index f55c8cd31..64167c031 100644 --- a/src/main/java/dan200/computercraft/shared/computer/recipe/ComputerUpgradeRecipe.java +++ b/src/main/java/dan200/computercraft/shared/computer/recipe/ComputerUpgradeRecipe.java @@ -11,9 +11,8 @@ import dan200.computercraft.shared.computer.items.IComputerItem; import net.minecraft.item.ItemStack; import net.minecraft.recipe.Ingredient; import net.minecraft.recipe.RecipeSerializer; -import net.minecraft.util.DefaultedList; import net.minecraft.util.Identifier; - +import net.minecraft.util.collection.DefaultedList; import javax.annotation.Nonnull; public class ComputerUpgradeRecipe extends ComputerFamilyRecipe @@ -37,7 +36,7 @@ public class ComputerUpgradeRecipe extends ComputerFamilyRecipe return SERIALIZER; } - public static final RecipeSerializer SERIALIZER = new Serializer() + public static final RecipeSerializer SERIALIZER = new dan200.computercraft.shared.computer.recipe.ComputerFamilyRecipe.Serializer() { @Override protected ComputerUpgradeRecipe create( Identifier identifier, String group, int width, int height, DefaultedList ingredients, ItemStack result, ComputerFamily family ) diff --git a/src/main/java/dan200/computercraft/shared/media/items/ItemDisk.java b/src/main/java/dan200/computercraft/shared/media/items/ItemDisk.java index 0e5e24c2b..345d253d1 100644 --- a/src/main/java/dan200/computercraft/shared/media/items/ItemDisk.java +++ b/src/main/java/dan200/computercraft/shared/media/items/ItemDisk.java @@ -20,8 +20,8 @@ import net.minecraft.nbt.CompoundTag; import net.minecraft.text.LiteralText; import net.minecraft.text.Text; import net.minecraft.text.TranslatableText; -import net.minecraft.util.DefaultedList; import net.minecraft.util.Formatting; +import net.minecraft.util.collection.DefaultedList; import net.minecraft.world.World; import javax.annotation.Nonnull; @@ -106,7 +106,7 @@ public class ItemDisk extends Item implements IMedia, IColouredItem public static int getDiskID( @Nonnull ItemStack stack ) { CompoundTag nbt = stack.getTag(); - return nbt != null && nbt.containsKey( NBT_ID ) ? nbt.getInt( NBT_ID ) : -1; + return nbt != null && nbt.contains( NBT_ID ) ? nbt.getInt( NBT_ID ) : -1; } private static void setDiskID( @Nonnull ItemStack stack, int id ) diff --git a/src/main/java/dan200/computercraft/shared/media/items/ItemPrintout.java b/src/main/java/dan200/computercraft/shared/media/items/ItemPrintout.java index 133f66e96..a3fb6c45e 100644 --- a/src/main/java/dan200/computercraft/shared/media/items/ItemPrintout.java +++ b/src/main/java/dan200/computercraft/shared/media/items/ItemPrintout.java @@ -118,13 +118,13 @@ public class ItemPrintout extends Item public static String getTitle( @Nonnull ItemStack stack ) { CompoundTag nbt = stack.getTag(); - return nbt != null && nbt.containsKey( NBT_TITLE ) ? nbt.getString( NBT_TITLE ) : null; + return nbt != null && nbt.contains( NBT_TITLE ) ? nbt.getString( NBT_TITLE ) : null; } public static int getPageCount( @Nonnull ItemStack stack ) { CompoundTag nbt = stack.getTag(); - return nbt != null && nbt.containsKey( NBT_PAGES ) ? nbt.getInt( NBT_PAGES ) : 1; + return nbt != null && nbt.contains( NBT_PAGES ) ? nbt.getInt( NBT_PAGES ) : 1; } public static String[] getText( @Nonnull ItemStack stack ) diff --git a/src/main/java/dan200/computercraft/shared/media/items/ItemTreasureDisk.java b/src/main/java/dan200/computercraft/shared/media/items/ItemTreasureDisk.java index 67b9800b7..f90b34f12 100644 --- a/src/main/java/dan200/computercraft/shared/media/items/ItemTreasureDisk.java +++ b/src/main/java/dan200/computercraft/shared/media/items/ItemTreasureDisk.java @@ -19,7 +19,7 @@ import net.minecraft.item.ItemStack; import net.minecraft.nbt.CompoundTag; import net.minecraft.text.LiteralText; import net.minecraft.text.Text; -import net.minecraft.util.DefaultedList; +import net.minecraft.util.collection.DefaultedList; import net.minecraft.world.World; import javax.annotation.Nonnull; import javax.annotation.Nullable; @@ -112,19 +112,19 @@ public class ItemTreasureDisk extends Item implements IMedia private static String getTitle( @Nonnull ItemStack stack ) { CompoundTag nbt = stack.getTag(); - return nbt != null && nbt.containsKey( NBT_TITLE ) ? nbt.getString( NBT_TITLE ) : "'alongtimeago' by dan200"; + return nbt != null && nbt.contains( NBT_TITLE ) ? nbt.getString( NBT_TITLE ) : "'alongtimeago' by dan200"; } @Nonnull private static String getSubPath( @Nonnull ItemStack stack ) { CompoundTag nbt = stack.getTag(); - return nbt != null && nbt.containsKey( NBT_SUB_PATH ) ? nbt.getString( NBT_SUB_PATH ) : "dan200/alongtimeago"; + return nbt != null && nbt.contains( NBT_SUB_PATH ) ? nbt.getString( NBT_SUB_PATH ) : "dan200/alongtimeago"; } public static int getColour( @Nonnull ItemStack stack ) { CompoundTag nbt = stack.getTag(); - return nbt != null && nbt.containsKey( NBT_COLOUR ) ? nbt.getInt( NBT_COLOUR ) : Colour.Blue.getHex(); + return nbt != null && nbt.contains( NBT_COLOUR ) ? nbt.getInt( NBT_COLOUR ) : Colour.Blue.getHex(); } } diff --git a/src/main/java/dan200/computercraft/shared/media/recipes/DiskRecipe.java b/src/main/java/dan200/computercraft/shared/media/recipes/DiskRecipe.java index 1ca023f8f..24b9053e0 100644 --- a/src/main/java/dan200/computercraft/shared/media/recipes/DiskRecipe.java +++ b/src/main/java/dan200/computercraft/shared/media/recipes/DiskRecipe.java @@ -39,9 +39,9 @@ public class DiskRecipe extends SpecialCraftingRecipe boolean paperFound = false; boolean redstoneFound = false; - for( int i = 0; i < inv.getInvSize(); i++ ) + for( int i = 0; i < inv.size(); i++ ) { - ItemStack stack = inv.getInvStack( i ); + ItemStack stack = inv.getStack( i ); if( !stack.isEmpty() ) { @@ -71,9 +71,9 @@ public class DiskRecipe extends SpecialCraftingRecipe { ColourTracker tracker = new ColourTracker(); - for( int i = 0; i < inv.getInvSize(); i++ ) + for( int i = 0; i < inv.size(); i++ ) { - ItemStack stack = inv.getInvStack( i ); + ItemStack stack = inv.getStack( i ); if( stack.isEmpty() ) continue; diff --git a/src/main/java/dan200/computercraft/shared/media/recipes/PrintoutRecipe.java b/src/main/java/dan200/computercraft/shared/media/recipes/PrintoutRecipe.java index 5d307546b..56b5cc0a4 100644 --- a/src/main/java/dan200/computercraft/shared/media/recipes/PrintoutRecipe.java +++ b/src/main/java/dan200/computercraft/shared/media/recipes/PrintoutRecipe.java @@ -64,7 +64,7 @@ public final class PrintoutRecipe extends SpecialCraftingRecipe { for( int x = 0; x < inventory.getWidth(); x++ ) { - ItemStack stack = inventory.getInvStack( x + y * inventory.getWidth() ); + ItemStack stack = inventory.getStack( x + y * inventory.getWidth() ); if( !stack.isEmpty() ) { if( stack.getItem() instanceof ItemPrintout && ((ItemPrintout) stack.getItem()).getType() != ItemPrintout.Type.BOOK ) diff --git a/src/main/java/dan200/computercraft/shared/mixin/MixinFirstPersonRenderer.java b/src/main/java/dan200/computercraft/shared/mixin/MixinFirstPersonRenderer.java index 2482e247d..8efd7f4c9 100644 --- a/src/main/java/dan200/computercraft/shared/mixin/MixinFirstPersonRenderer.java +++ b/src/main/java/dan200/computercraft/shared/mixin/MixinFirstPersonRenderer.java @@ -12,7 +12,7 @@ import dan200.computercraft.shared.media.items.ItemPrintout; import dan200.computercraft.shared.mixed.MixedFirstPersonRenderer; import dan200.computercraft.shared.pocket.items.ItemPocketComputer; import net.minecraft.client.network.AbstractClientPlayerEntity; -import net.minecraft.client.render.FirstPersonRenderer; +import net.minecraft.client.render.item.HeldItemRenderer; import net.minecraft.item.ItemStack; import net.minecraft.util.Arm; import net.minecraft.util.Hand; @@ -22,7 +22,7 @@ import org.spongepowered.asm.mixin.injection.At; import org.spongepowered.asm.mixin.injection.Inject; import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; -@Mixin( FirstPersonRenderer.class ) +@Mixin( HeldItemRenderer.class ) public class MixinFirstPersonRenderer implements MixedFirstPersonRenderer { @Shadow diff --git a/src/main/java/dan200/computercraft/shared/mixin/MixinWorld.java b/src/main/java/dan200/computercraft/shared/mixin/MixinWorld.java index 657713068..308488c0b 100644 --- a/src/main/java/dan200/computercraft/shared/mixin/MixinWorld.java +++ b/src/main/java/dan200/computercraft/shared/mixin/MixinWorld.java @@ -33,7 +33,7 @@ public class MixinWorld @Inject( method = "setBlockEntity", at = @At( "HEAD" ) ) public void setBlockEntity( BlockPos pos, @Nullable BlockEntity entity, CallbackInfo info ) { - if( !World.isHeightInvalid( pos ) && entity != null && !entity.isInvalid() && iteratingTickingBlockEntities ) + if( !World.isHeightInvalid( pos ) && entity != null && !entity.isRemoved() && iteratingTickingBlockEntities ) { setWorld( entity, this ); } @@ -50,6 +50,6 @@ public class MixinWorld private static void setWorld( BlockEntity entity, Object world ) { - if( entity.getWorld() != world && entity instanceof TileGeneric ) entity.setWorld( (World) world ); + if( entity.getWorld() != world && entity instanceof TileGeneric ) entity.setLocation( (World) world ); } } diff --git a/src/main/java/dan200/computercraft/shared/network/NetworkHandler.java b/src/main/java/dan200/computercraft/shared/network/NetworkHandler.java index d76e0b299..d60dd8d67 100644 --- a/src/main/java/dan200/computercraft/shared/network/NetworkHandler.java +++ b/src/main/java/dan200/computercraft/shared/network/NetworkHandler.java @@ -20,13 +20,13 @@ import net.fabricmc.fabric.api.network.PacketContext; import net.fabricmc.fabric.api.network.ServerSidePacketRegistry; import net.fabricmc.loader.api.FabricLoader; import net.minecraft.client.MinecraftClient; -import net.minecraft.client.network.packet.CustomPayloadS2CPacket; import net.minecraft.entity.player.PlayerEntity; +import net.minecraft.network.PacketByteBuf; +import net.minecraft.network.packet.c2s.play.CustomPayloadC2SPacket; +import net.minecraft.network.packet.s2c.play.CustomPayloadS2CPacket; import net.minecraft.server.MinecraftServer; import net.minecraft.server.network.ServerPlayerEntity; -import net.minecraft.server.network.packet.CustomPayloadC2SPacket; import net.minecraft.util.Identifier; -import net.minecraft.util.PacketByteBuf; import net.minecraft.util.math.Vec3d; import net.minecraft.world.World; diff --git a/src/main/java/dan200/computercraft/shared/network/NetworkMessage.java b/src/main/java/dan200/computercraft/shared/network/NetworkMessage.java index e35cbeee4..ed77457d6 100644 --- a/src/main/java/dan200/computercraft/shared/network/NetworkMessage.java +++ b/src/main/java/dan200/computercraft/shared/network/NetworkMessage.java @@ -7,8 +7,7 @@ package dan200.computercraft.shared.network; import net.fabricmc.fabric.api.network.PacketContext; -import net.minecraft.util.PacketByteBuf; - +import net.minecraft.network.PacketByteBuf; import javax.annotation.Nonnull; /** diff --git a/src/main/java/dan200/computercraft/shared/network/client/ChatTableClientMessage.java b/src/main/java/dan200/computercraft/shared/network/client/ChatTableClientMessage.java index 61cd0b2ab..42f786872 100644 --- a/src/main/java/dan200/computercraft/shared/network/client/ChatTableClientMessage.java +++ b/src/main/java/dan200/computercraft/shared/network/client/ChatTableClientMessage.java @@ -12,9 +12,8 @@ import dan200.computercraft.shared.network.NetworkMessage; import net.fabricmc.api.EnvType; import net.fabricmc.api.Environment; import net.fabricmc.fabric.api.network.PacketContext; +import net.minecraft.network.PacketByteBuf; import net.minecraft.text.Text; -import net.minecraft.util.PacketByteBuf; - import javax.annotation.Nonnull; public class ChatTableClientMessage implements NetworkMessage diff --git a/src/main/java/dan200/computercraft/shared/network/client/ComputerClientMessage.java b/src/main/java/dan200/computercraft/shared/network/client/ComputerClientMessage.java index b2ddb693f..630dd41c1 100644 --- a/src/main/java/dan200/computercraft/shared/network/client/ComputerClientMessage.java +++ b/src/main/java/dan200/computercraft/shared/network/client/ComputerClientMessage.java @@ -9,9 +9,8 @@ package dan200.computercraft.shared.network.client; import dan200.computercraft.ComputerCraft; import dan200.computercraft.shared.computer.core.ClientComputer; import dan200.computercraft.shared.network.NetworkMessage; -import net.minecraft.util.PacketByteBuf; - import javax.annotation.Nonnull; +import net.minecraft.network.PacketByteBuf; /** * A packet, which performs an action on a {@link ClientComputer}. diff --git a/src/main/java/dan200/computercraft/shared/network/client/ComputerDataClientMessage.java b/src/main/java/dan200/computercraft/shared/network/client/ComputerDataClientMessage.java index 9933af4bc..7b3409466 100644 --- a/src/main/java/dan200/computercraft/shared/network/client/ComputerDataClientMessage.java +++ b/src/main/java/dan200/computercraft/shared/network/client/ComputerDataClientMessage.java @@ -10,8 +10,7 @@ import dan200.computercraft.shared.computer.core.ComputerState; import dan200.computercraft.shared.computer.core.ServerComputer; import net.fabricmc.fabric.api.network.PacketContext; import net.minecraft.nbt.CompoundTag; -import net.minecraft.util.PacketByteBuf; - +import net.minecraft.network.PacketByteBuf; import javax.annotation.Nonnull; /** diff --git a/src/main/java/dan200/computercraft/shared/network/client/ComputerTerminalClientMessage.java b/src/main/java/dan200/computercraft/shared/network/client/ComputerTerminalClientMessage.java index bc1b6ec95..47773131e 100644 --- a/src/main/java/dan200/computercraft/shared/network/client/ComputerTerminalClientMessage.java +++ b/src/main/java/dan200/computercraft/shared/network/client/ComputerTerminalClientMessage.java @@ -8,8 +8,7 @@ package dan200.computercraft.shared.network.client; import net.fabricmc.fabric.api.network.PacketContext; import net.minecraft.nbt.CompoundTag; -import net.minecraft.util.PacketByteBuf; - +import net.minecraft.network.PacketByteBuf; import javax.annotation.Nonnull; public class ComputerTerminalClientMessage extends ComputerClientMessage diff --git a/src/main/java/dan200/computercraft/shared/network/client/PlayRecordClientMessage.java b/src/main/java/dan200/computercraft/shared/network/client/PlayRecordClientMessage.java index d385f29ab..9a5a0e1f5 100644 --- a/src/main/java/dan200/computercraft/shared/network/client/PlayRecordClientMessage.java +++ b/src/main/java/dan200/computercraft/shared/network/client/PlayRecordClientMessage.java @@ -11,8 +11,8 @@ import net.fabricmc.api.EnvType; import net.fabricmc.api.Environment; import net.fabricmc.fabric.api.network.PacketContext; import net.minecraft.client.MinecraftClient; +import net.minecraft.network.PacketByteBuf; import net.minecraft.sound.SoundEvent; -import net.minecraft.util.PacketByteBuf; import net.minecraft.util.math.BlockPos; import net.minecraft.util.registry.Registry; diff --git a/src/main/java/dan200/computercraft/shared/network/container/ContainerType.java b/src/main/java/dan200/computercraft/shared/network/container/ContainerType.java index 73281d437..87deb1b87 100644 --- a/src/main/java/dan200/computercraft/shared/network/container/ContainerType.java +++ b/src/main/java/dan200/computercraft/shared/network/container/ContainerType.java @@ -9,18 +9,17 @@ package dan200.computercraft.shared.network.container; import net.fabricmc.fabric.api.client.screen.ScreenProviderRegistry; import net.fabricmc.fabric.api.container.ContainerProviderRegistry; import net.minecraft.client.MinecraftClient; -import net.minecraft.client.gui.screen.ingame.AbstractContainerScreen; -import net.minecraft.container.Container; +import net.minecraft.client.gui.screen.ingame.HandledScreen; import net.minecraft.entity.player.PlayerEntity; import net.minecraft.entity.player.PlayerInventory; +import net.minecraft.network.PacketByteBuf; +import net.minecraft.screen.ScreenHandler; import net.minecraft.util.Identifier; -import net.minecraft.util.PacketByteBuf; - import javax.annotation.Nonnull; import java.util.function.BiFunction; import java.util.function.Supplier; -public interface ContainerType +public interface ContainerType { @Nonnull Identifier getId(); @@ -34,7 +33,7 @@ public interface ContainerType ContainerProviderRegistry.INSTANCE.openContainer( getId(), player, this::toBytes ); } - static > void register( Supplier containerType, ContainerFactory factory ) + static > void register( Supplier containerType, ContainerFactory factory ) { ContainerProviderRegistry.INSTANCE.registerFactory( containerType.get().getId(), ( id, type, player, packet ) -> { T container = containerType.get(); @@ -43,7 +42,7 @@ public interface ContainerType } ); } - static > void registerGui( Supplier containerType, ContainerFactory> factory ) + static > void registerGui( Supplier containerType, ContainerFactory> factory ) { ScreenProviderRegistry.INSTANCE.registerFactory( containerType.get().getId(), ( id, type, player, packet ) -> { T container = containerType.get(); @@ -52,7 +51,7 @@ public interface ContainerType } ); } - static > void registerGui( Supplier containerType, BiFunction> factory ) + static > void registerGui( Supplier containerType, BiFunction> factory ) { ScreenProviderRegistry.INSTANCE.registerFactory( containerType.get().getId(), container -> factory.apply( container, MinecraftClient.getInstance().player.inventory ) ); diff --git a/src/main/java/dan200/computercraft/shared/network/container/PocketComputerContainerType.java b/src/main/java/dan200/computercraft/shared/network/container/PocketComputerContainerType.java index 425843e60..890752c28 100644 --- a/src/main/java/dan200/computercraft/shared/network/container/PocketComputerContainerType.java +++ b/src/main/java/dan200/computercraft/shared/network/container/PocketComputerContainerType.java @@ -8,10 +8,9 @@ package dan200.computercraft.shared.network.container; import dan200.computercraft.ComputerCraft; import dan200.computercraft.shared.pocket.inventory.ContainerPocketComputer; +import net.minecraft.network.PacketByteBuf; import net.minecraft.util.Hand; import net.minecraft.util.Identifier; -import net.minecraft.util.PacketByteBuf; - import javax.annotation.Nonnull; /** diff --git a/src/main/java/dan200/computercraft/shared/network/container/PrintoutContainerType.java b/src/main/java/dan200/computercraft/shared/network/container/PrintoutContainerType.java index 1052f7465..bebc8da21 100644 --- a/src/main/java/dan200/computercraft/shared/network/container/PrintoutContainerType.java +++ b/src/main/java/dan200/computercraft/shared/network/container/PrintoutContainerType.java @@ -8,10 +8,9 @@ package dan200.computercraft.shared.network.container; import dan200.computercraft.ComputerCraft; import dan200.computercraft.shared.common.ContainerHeldItem; +import net.minecraft.network.PacketByteBuf; import net.minecraft.util.Hand; import net.minecraft.util.Identifier; -import net.minecraft.util.PacketByteBuf; - import javax.annotation.Nonnull; /** diff --git a/src/main/java/dan200/computercraft/shared/network/container/TileEntityContainerType.java b/src/main/java/dan200/computercraft/shared/network/container/TileEntityContainerType.java index 75689979e..504b0c4b0 100644 --- a/src/main/java/dan200/computercraft/shared/network/container/TileEntityContainerType.java +++ b/src/main/java/dan200/computercraft/shared/network/container/TileEntityContainerType.java @@ -12,10 +12,10 @@ import dan200.computercraft.shared.peripheral.diskdrive.ContainerDiskDrive; import dan200.computercraft.shared.peripheral.printer.ContainerPrinter; import dan200.computercraft.shared.turtle.inventory.ContainerTurtle; import net.minecraft.block.entity.BlockEntity; -import net.minecraft.container.Container; import net.minecraft.entity.player.PlayerEntity; +import net.minecraft.network.PacketByteBuf; +import net.minecraft.screen.ScreenHandler; import net.minecraft.util.Identifier; -import net.minecraft.util.PacketByteBuf; import net.minecraft.util.math.BlockPos; import javax.annotation.Nonnull; @@ -27,7 +27,7 @@ import javax.annotation.Nonnull; * @see dan200.computercraft.shared.peripheral.printer.TilePrinter * @see dan200.computercraft.shared.computer.blocks.TileComputer */ -public final class TileEntityContainerType implements ContainerType +public final class TileEntityContainerType implements ContainerType { private static final Identifier DISK_DRIVE = new Identifier( ComputerCraft.MOD_ID, "disk_drive" ); private static final Identifier PRINTER = new Identifier( ComputerCraft.MOD_ID, "printer" ); diff --git a/src/main/java/dan200/computercraft/shared/network/container/ViewComputerContainerType.java b/src/main/java/dan200/computercraft/shared/network/container/ViewComputerContainerType.java index 03898ce27..4799ae79a 100644 --- a/src/main/java/dan200/computercraft/shared/network/container/ViewComputerContainerType.java +++ b/src/main/java/dan200/computercraft/shared/network/container/ViewComputerContainerType.java @@ -11,9 +11,8 @@ import dan200.computercraft.core.terminal.Terminal; import dan200.computercraft.shared.computer.core.ComputerFamily; import dan200.computercraft.shared.computer.core.ServerComputer; import dan200.computercraft.shared.computer.inventory.ContainerViewComputer; +import net.minecraft.network.PacketByteBuf; import net.minecraft.util.Identifier; -import net.minecraft.util.PacketByteBuf; - import javax.annotation.Nonnull; /** diff --git a/src/main/java/dan200/computercraft/shared/network/server/ComputerActionServerMessage.java b/src/main/java/dan200/computercraft/shared/network/server/ComputerActionServerMessage.java index 24c4e3dfb..2b5dc9939 100644 --- a/src/main/java/dan200/computercraft/shared/network/server/ComputerActionServerMessage.java +++ b/src/main/java/dan200/computercraft/shared/network/server/ComputerActionServerMessage.java @@ -8,9 +8,8 @@ package dan200.computercraft.shared.network.server; import dan200.computercraft.shared.computer.core.IContainerComputer; import dan200.computercraft.shared.computer.core.ServerComputer; -import net.minecraft.util.PacketByteBuf; - import javax.annotation.Nonnull; +import net.minecraft.network.PacketByteBuf; public class ComputerActionServerMessage extends ComputerServerMessage { diff --git a/src/main/java/dan200/computercraft/shared/network/server/ComputerServerMessage.java b/src/main/java/dan200/computercraft/shared/network/server/ComputerServerMessage.java index 1b1711e6d..d36af723d 100644 --- a/src/main/java/dan200/computercraft/shared/network/server/ComputerServerMessage.java +++ b/src/main/java/dan200/computercraft/shared/network/server/ComputerServerMessage.java @@ -11,8 +11,7 @@ import dan200.computercraft.shared.computer.core.IContainerComputer; import dan200.computercraft.shared.computer.core.ServerComputer; import dan200.computercraft.shared.network.NetworkMessage; import net.fabricmc.fabric.api.network.PacketContext; -import net.minecraft.util.PacketByteBuf; - +import net.minecraft.network.PacketByteBuf; import javax.annotation.Nonnull; /** diff --git a/src/main/java/dan200/computercraft/shared/network/server/KeyEventServerMessage.java b/src/main/java/dan200/computercraft/shared/network/server/KeyEventServerMessage.java index b325251f9..ee8a5a9b1 100644 --- a/src/main/java/dan200/computercraft/shared/network/server/KeyEventServerMessage.java +++ b/src/main/java/dan200/computercraft/shared/network/server/KeyEventServerMessage.java @@ -9,9 +9,8 @@ package dan200.computercraft.shared.network.server; import dan200.computercraft.shared.computer.core.IContainerComputer; import dan200.computercraft.shared.computer.core.InputState; import dan200.computercraft.shared.computer.core.ServerComputer; -import net.minecraft.util.PacketByteBuf; - import javax.annotation.Nonnull; +import net.minecraft.network.PacketByteBuf; public class KeyEventServerMessage extends ComputerServerMessage { diff --git a/src/main/java/dan200/computercraft/shared/network/server/MouseEventServerMessage.java b/src/main/java/dan200/computercraft/shared/network/server/MouseEventServerMessage.java index 8289107b5..2ffabbf38 100644 --- a/src/main/java/dan200/computercraft/shared/network/server/MouseEventServerMessage.java +++ b/src/main/java/dan200/computercraft/shared/network/server/MouseEventServerMessage.java @@ -9,9 +9,8 @@ package dan200.computercraft.shared.network.server; import dan200.computercraft.shared.computer.core.IContainerComputer; import dan200.computercraft.shared.computer.core.InputState; import dan200.computercraft.shared.computer.core.ServerComputer; -import net.minecraft.util.PacketByteBuf; - import javax.annotation.Nonnull; +import net.minecraft.network.PacketByteBuf; public class MouseEventServerMessage extends ComputerServerMessage { diff --git a/src/main/java/dan200/computercraft/shared/network/server/QueueEventServerMessage.java b/src/main/java/dan200/computercraft/shared/network/server/QueueEventServerMessage.java index cedd1ba96..14e3f2326 100644 --- a/src/main/java/dan200/computercraft/shared/network/server/QueueEventServerMessage.java +++ b/src/main/java/dan200/computercraft/shared/network/server/QueueEventServerMessage.java @@ -10,8 +10,7 @@ import dan200.computercraft.shared.computer.core.IContainerComputer; import dan200.computercraft.shared.computer.core.ServerComputer; import dan200.computercraft.shared.util.NBTUtil; import net.minecraft.nbt.CompoundTag; -import net.minecraft.util.PacketByteBuf; - +import net.minecraft.network.PacketByteBuf; import javax.annotation.Nonnull; import javax.annotation.Nullable; diff --git a/src/main/java/dan200/computercraft/shared/network/server/RequestComputerMessage.java b/src/main/java/dan200/computercraft/shared/network/server/RequestComputerMessage.java index 9a246c43e..aae3f4eee 100644 --- a/src/main/java/dan200/computercraft/shared/network/server/RequestComputerMessage.java +++ b/src/main/java/dan200/computercraft/shared/network/server/RequestComputerMessage.java @@ -10,8 +10,7 @@ import dan200.computercraft.ComputerCraft; import dan200.computercraft.shared.computer.core.ServerComputer; import dan200.computercraft.shared.network.NetworkMessage; import net.fabricmc.fabric.api.network.PacketContext; -import net.minecraft.util.PacketByteBuf; - +import net.minecraft.network.PacketByteBuf; import javax.annotation.Nonnull; public class RequestComputerMessage implements NetworkMessage diff --git a/src/main/java/dan200/computercraft/shared/peripheral/diskdrive/BlockDiskDrive.java b/src/main/java/dan200/computercraft/shared/peripheral/diskdrive/BlockDiskDrive.java index 093470234..070f64afb 100644 --- a/src/main/java/dan200/computercraft/shared/peripheral/diskdrive/BlockDiskDrive.java +++ b/src/main/java/dan200/computercraft/shared/peripheral/diskdrive/BlockDiskDrive.java @@ -13,7 +13,7 @@ import net.minecraft.block.entity.BlockEntity; import net.minecraft.entity.LivingEntity; import net.minecraft.item.ItemPlacementContext; import net.minecraft.item.ItemStack; -import net.minecraft.state.StateFactory; +import net.minecraft.state.StateManager; import net.minecraft.state.property.DirectionProperty; import net.minecraft.state.property.EnumProperty; import net.minecraft.state.property.Properties; @@ -31,13 +31,13 @@ public class BlockDiskDrive extends BlockGeneric public BlockDiskDrive( Settings settings ) { super( settings, TileDiskDrive.FACTORY ); - setDefaultState( getStateFactory().getDefaultState() + setDefaultState( getStateManager().getDefaultState() .with( FACING, Direction.NORTH ) .with( STATE, DiskDriveState.EMPTY ) ); } @Override - protected void appendProperties( StateFactory.Builder properties ) + protected void appendProperties( StateManager.Builder properties ) { properties.add( FACING, STATE ); } diff --git a/src/main/java/dan200/computercraft/shared/peripheral/diskdrive/ContainerDiskDrive.java b/src/main/java/dan200/computercraft/shared/peripheral/diskdrive/ContainerDiskDrive.java index 78f128894..5617dc1de 100644 --- a/src/main/java/dan200/computercraft/shared/peripheral/diskdrive/ContainerDiskDrive.java +++ b/src/main/java/dan200/computercraft/shared/peripheral/diskdrive/ContainerDiskDrive.java @@ -6,23 +6,22 @@ package dan200.computercraft.shared.peripheral.diskdrive; -import net.minecraft.container.Container; -import net.minecraft.container.Slot; import net.minecraft.entity.player.PlayerEntity; import net.minecraft.entity.player.PlayerInventory; -import net.minecraft.inventory.BasicInventory; import net.minecraft.inventory.Inventory; +import net.minecraft.inventory.SimpleInventory; import net.minecraft.item.ItemStack; - +import net.minecraft.screen.ScreenHandler; +import net.minecraft.screen.slot.Slot; import javax.annotation.Nonnull; -public class ContainerDiskDrive extends Container +public class ContainerDiskDrive extends ScreenHandler { private final Inventory m_diskDrive; public ContainerDiskDrive( int id, PlayerInventory player ) { - this( id, player, new BasicInventory( TileDiskDrive.INVENTORY_SIZE ) ); + this( id, player, new SimpleInventory( TileDiskDrive.INVENTORY_SIZE ) ); } public ContainerDiskDrive( int id, PlayerInventory playerInventory, Inventory diskDrive ) @@ -50,13 +49,13 @@ public class ContainerDiskDrive extends Container @Override public boolean canUse( @Nonnull PlayerEntity player ) { - return m_diskDrive.canPlayerUseInv( player ); + return m_diskDrive.canPlayerUse( player ); } @Override public ItemStack transferSlot( PlayerEntity player, int slotIndex ) { - Slot slot = slotList.get( slotIndex ); + Slot slot = slots.get( slotIndex ); if( slot == null || !slot.hasStack() ) return ItemStack.EMPTY; ItemStack existing = slot.getStack(); diff --git a/src/main/java/dan200/computercraft/shared/peripheral/diskdrive/TileDiskDrive.java b/src/main/java/dan200/computercraft/shared/peripheral/diskdrive/TileDiskDrive.java index 7a9dd0c42..f9edb3e17 100644 --- a/src/main/java/dan200/computercraft/shared/peripheral/diskdrive/TileDiskDrive.java +++ b/src/main/java/dan200/computercraft/shared/peripheral/diskdrive/TileDiskDrive.java @@ -92,7 +92,7 @@ public final class TileDiskDrive extends TileGeneric implements DefaultInventory // Try to put a disk into the drive ItemStack disk = player.getStackInHand( hand ); if( disk.isEmpty() ) return false; - if( !getWorld().isClient && getInvStack( 0 ).isEmpty() && MediaProviders.get( disk ) != null ) + if( !getWorld().isClient && getStack( 0 ).isEmpty() && MediaProviders.get( disk ) != null ) { setDiskStack( disk ); player.setStackInHand( hand, ItemStack.EMPTY ); @@ -116,8 +116,8 @@ public final class TileDiskDrive extends TileGeneric implements DefaultInventory public void fromTag( CompoundTag nbt ) { super.fromTag( nbt ); - customName = nbt.containsKey( NBT_NAME ) ? LiteralText.Serializer.fromJson( nbt.getString( NBT_NAME ) ) : null; - if( nbt.containsKey( NBT_ITEM ) ) + customName = nbt.contains( NBT_NAME ) ? LiteralText.Serializer.fromJson( nbt.getString( NBT_NAME ) ) : null; + if( nbt.contains( NBT_ITEM ) ) { CompoundTag item = nbt.getCompound( NBT_ITEM ); m_diskStack = ItemStack.fromTag( item ); @@ -182,26 +182,26 @@ public final class TileDiskDrive extends TileGeneric implements DefaultInventory // Inventory implementation @Override - public int getInvSize() + public int size() { return INVENTORY_SIZE; } @Override - public boolean isInvEmpty() + public boolean isEmpty() { return m_diskStack.isEmpty(); } @Override - public ItemStack getInvStack( int slot ) + public ItemStack getStack( int slot ) { return m_diskStack; } @Nonnull @Override - public ItemStack removeInvStack( int slot ) + public ItemStack removeStack( int slot ) { ItemStack result = m_diskStack; m_diskStack = ItemStack.EMPTY; @@ -212,24 +212,24 @@ public final class TileDiskDrive extends TileGeneric implements DefaultInventory @Nonnull @Override - public ItemStack takeInvStack( int slot, int count ) + public ItemStack removeStack( int slot, int count ) { if( m_diskStack.isEmpty() ) return ItemStack.EMPTY; if( m_diskStack.getCount() <= count ) { ItemStack disk = m_diskStack; - setInvStack( slot, ItemStack.EMPTY ); + setStack( slot, ItemStack.EMPTY ); return disk; } ItemStack part = m_diskStack.split( count ); - setInvStack( slot, m_diskStack.isEmpty() ? ItemStack.EMPTY : m_diskStack ); + setStack( slot, m_diskStack.isEmpty() ? ItemStack.EMPTY : m_diskStack ); return part; } @Override - public void setInvStack( int slot, @Nonnull ItemStack stack ) + public void setStack( int slot, @Nonnull ItemStack stack ) { if( getWorld().isClient ) { @@ -285,7 +285,7 @@ public final class TileDiskDrive extends TileGeneric implements DefaultInventory } @Override - public boolean canPlayerUseInv( PlayerEntity player ) + public boolean canPlayerUse( PlayerEntity player ) { return isUsable( player, false ); } @@ -293,7 +293,7 @@ public final class TileDiskDrive extends TileGeneric implements DefaultInventory @Override public void clear() { - setInvStack( 0, ItemStack.EMPTY ); + setStack( 0, ItemStack.EMPTY ); } @Override @@ -305,12 +305,12 @@ public final class TileDiskDrive extends TileGeneric implements DefaultInventory @Nonnull public ItemStack getDiskStack() { - return getInvStack( 0 ); + return getStack( 0 ); } public void setDiskStack( @Nonnull ItemStack stack ) { - setInvStack( 0, stack ); + setStack( 0, stack ); } public IMedia getDiskMedia() @@ -442,7 +442,7 @@ public final class TileDiskDrive extends TileGeneric implements DefaultInventory private void updateBlockState() { - if( invalid ) return; + if( removed ) return; if( !m_diskStack.isEmpty() ) { @@ -489,15 +489,15 @@ public final class TileDiskDrive extends TileGeneric implements DefaultInventory entityitem.setVelocity( xOff * 0.15, 0.0, zOff * 0.15 ); getWorld().spawnEntity( entityitem ); - if( !destroyed ) getWorld().playGlobalEvent( 1000, getPos(), 0 ); + if( !destroyed ) getWorld().syncGlobalEvent( 1000, getPos(), 0 ); } @Override protected void readDescription( @Nonnull CompoundTag nbt ) { super.readDescription( nbt ); - customName = nbt.containsKey( NBT_NAME ) ? LiteralText.Serializer.fromJson( nbt.getString( NBT_NAME ) ) : null; - m_diskStack = nbt.containsKey( NBT_ITEM ) ? ItemStack.fromTag( nbt.getCompound( NBT_ITEM ) ) : ItemStack.EMPTY; + customName = nbt.contains( NBT_NAME ) ? LiteralText.Serializer.fromJson( nbt.getString( NBT_NAME ) ) : null; + m_diskStack = nbt.contains( NBT_ITEM ) ? ItemStack.fromTag( nbt.getCompound( NBT_ITEM ) ) : ItemStack.EMPTY; updateBlock(); } diff --git a/src/main/java/dan200/computercraft/shared/peripheral/modem/wired/BlockCable.java b/src/main/java/dan200/computercraft/shared/peripheral/modem/wired/BlockCable.java index ba2362513..414ab7e56 100644 --- a/src/main/java/dan200/computercraft/shared/peripheral/modem/wired/BlockCable.java +++ b/src/main/java/dan200/computercraft/shared/peripheral/modem/wired/BlockCable.java @@ -16,14 +16,14 @@ import net.fabricmc.api.EnvType; import net.fabricmc.api.Environment; import net.minecraft.block.Block; import net.minecraft.block.BlockState; +import net.minecraft.block.ShapeContext; import net.minecraft.block.entity.BlockEntity; import net.minecraft.client.MinecraftClient; -import net.minecraft.entity.EntityContext; import net.minecraft.entity.LivingEntity; import net.minecraft.fluid.FluidState; import net.minecraft.item.ItemPlacementContext; import net.minecraft.item.ItemStack; -import net.minecraft.state.StateFactory; +import net.minecraft.state.StateManager; import net.minecraft.state.property.BooleanProperty; import net.minecraft.state.property.EnumProperty; import net.minecraft.util.hit.HitResult; @@ -31,10 +31,9 @@ import net.minecraft.util.math.BlockPos; import net.minecraft.util.math.Direction; import net.minecraft.util.shape.VoxelShape; import net.minecraft.world.BlockView; -import net.minecraft.world.IWorld; -import net.minecraft.world.ViewableWorld; +import net.minecraft.world.CollisionView; import net.minecraft.world.World; - +import net.minecraft.world.WorldAccess; import javax.annotation.Nonnull; import javax.annotation.Nullable; import java.util.EnumMap; @@ -62,7 +61,7 @@ public class BlockCable extends BlockGeneric implements WaterloggableBlock { super( settings, TileCable.FACTORY ); - setDefaultState( getStateFactory().getDefaultState() + setDefaultState( getStateManager().getDefaultState() .with( MODEM, CableModemVariant.None ) .with( CABLE, false ) .with( NORTH, false ).with( SOUTH, false ) @@ -73,7 +72,7 @@ public class BlockCable extends BlockGeneric implements WaterloggableBlock } @Override - protected void appendProperties( StateFactory.Builder builder ) + protected void appendProperties( StateManager.Builder builder ) { builder.add( MODEM, CABLE, NORTH, SOUTH, EAST, WEST, UP, DOWN, WATERLOGGED ); } @@ -92,7 +91,7 @@ public class BlockCable extends BlockGeneric implements WaterloggableBlock @Override @Deprecated - public VoxelShape getOutlineShape( BlockState state, BlockView world, BlockPos pos, EntityContext position ) + public VoxelShape getOutlineShape( BlockState state, BlockView world, BlockPos pos, ShapeContext position ) { return CableShapes.getShape( state ); } @@ -154,7 +153,7 @@ public class BlockCable extends BlockGeneric implements WaterloggableBlock if( modem == null ) return new ItemStack( ComputerCraft.Items.cable ); // We've a modem and cable, so try to work out which one we're interacting with - HitResult hit = MinecraftClient.getInstance().hitResult; + HitResult hit = MinecraftClient.getInstance().crosshairTarget; return hit != null && WorldUtil.isVecInside( CableShapes.getModemShape( state ), hit.getPos().subtract( pos.getX(), pos.getY(), pos.getZ() ) ) ? new ItemStack( ComputerCraft.Items.wiredModem ) : new ItemStack( ComputerCraft.Items.cable ); @@ -185,7 +184,7 @@ public class BlockCable extends BlockGeneric implements WaterloggableBlock @Nonnull @Override @Deprecated - public BlockState getStateForNeighborUpdate( BlockState state, Direction side, BlockState otherState, IWorld world, BlockPos pos, BlockPos otherPos ) + public BlockState getStateForNeighborUpdate( BlockState state, Direction side, BlockState otherState, WorldAccess world, BlockPos pos, BlockPos otherPos ) { updateWaterloggedPostPlacement( state, world, pos ); @@ -200,7 +199,7 @@ public class BlockCable extends BlockGeneric implements WaterloggableBlock @Override @Deprecated - public boolean canPlaceAt( BlockState state, ViewableWorld world, BlockPos pos ) + public boolean canPlaceAt( BlockState state, CollisionView world, BlockPos pos ) { Direction facing = state.get( MODEM ).getFacing(); if( facing == null ) return true; diff --git a/src/main/java/dan200/computercraft/shared/peripheral/modem/wired/BlockWiredModemFull.java b/src/main/java/dan200/computercraft/shared/peripheral/modem/wired/BlockWiredModemFull.java index 08bc88c77..2c893e47b 100644 --- a/src/main/java/dan200/computercraft/shared/peripheral/modem/wired/BlockWiredModemFull.java +++ b/src/main/java/dan200/computercraft/shared/peripheral/modem/wired/BlockWiredModemFull.java @@ -9,7 +9,7 @@ package dan200.computercraft.shared.peripheral.modem.wired; import dan200.computercraft.shared.common.BlockGeneric; import net.minecraft.block.Block; import net.minecraft.block.BlockState; -import net.minecraft.state.StateFactory; +import net.minecraft.state.StateManager; import net.minecraft.state.property.BooleanProperty; public class BlockWiredModemFull extends BlockGeneric @@ -20,14 +20,14 @@ public class BlockWiredModemFull extends BlockGeneric public BlockWiredModemFull( Settings settings ) { super( settings, TileWiredModemFull.FACTORY ); - setDefaultState( getStateFactory().getDefaultState() + setDefaultState( getStateManager().getDefaultState() .with( MODEM_ON, false ) .with( PERIPHERAL_ON, false ) ); } @Override - protected void appendProperties( StateFactory.Builder builder ) + protected void appendProperties( StateManager.Builder builder ) { builder.add( MODEM_ON, PERIPHERAL_ON ); } diff --git a/src/main/java/dan200/computercraft/shared/peripheral/modem/wired/ItemBlockCable.java b/src/main/java/dan200/computercraft/shared/peripheral/modem/wired/ItemBlockCable.java index db4ac4ef7..56ccd5a01 100644 --- a/src/main/java/dan200/computercraft/shared/peripheral/modem/wired/ItemBlockCable.java +++ b/src/main/java/dan200/computercraft/shared/peripheral/modem/wired/ItemBlockCable.java @@ -13,8 +13,8 @@ import net.minecraft.item.*; import net.minecraft.sound.BlockSoundGroup; import net.minecraft.sound.SoundCategory; import net.minecraft.util.ActionResult; -import net.minecraft.util.DefaultedList; -import net.minecraft.util.SystemUtil; +import net.minecraft.util.Util; +import net.minecraft.util.collection.DefaultedList; import net.minecraft.util.math.BlockPos; import net.minecraft.util.math.Direction; import net.minecraft.util.registry.Registry; @@ -68,7 +68,7 @@ public abstract class ItemBlockCable extends BlockItem { if( translationKey == null ) { - translationKey = SystemUtil.createTranslationKey( "block", Registry.ITEM.getId( this ) ); + translationKey = Util.createTranslationKey( "block", Registry.ITEM.getId( this ) ); } return translationKey; } diff --git a/src/main/java/dan200/computercraft/shared/peripheral/modem/wired/TileCable.java b/src/main/java/dan200/computercraft/shared/peripheral/modem/wired/TileCable.java index 8173642c8..461fc6551 100644 --- a/src/main/java/dan200/computercraft/shared/peripheral/modem/wired/TileCable.java +++ b/src/main/java/dan200/computercraft/shared/peripheral/modem/wired/TileCable.java @@ -143,16 +143,16 @@ public class TileCable extends TileGeneric implements IPeripheralTile */ @Override - public void invalidate() + public void markRemoved() { - super.invalidate(); + super.markRemoved(); onRemove(); } @Override - public void validate() + public void cancelRemoval() { - super.validate(); + super.cancelRemoval(); TickScheduler.schedule( this ); } @@ -200,7 +200,7 @@ public class TileCable extends TileGeneric implements IPeripheralTile { // Drop everything and remove block Block.dropStack( getWorld(), getPos(), new ItemStack( ComputerCraft.Items.wiredModem ) ); - getWorld().clearBlockState( getPos(), false ); + getWorld().removeBlock( getPos(), false ); // This'll call #destroy(), so we don't need to reset the network here. } @@ -238,12 +238,12 @@ public class TileCable extends TileGeneric implements IPeripheralTile { if( oldName != null ) { - player.addChatMessage( new TranslatableText( "chat.computercraft.wired_modem.peripheral_disconnected", + player.sendMessage( new TranslatableText( "chat.computercraft.wired_modem.peripheral_disconnected", CommandCopy.createCopyText( oldName ) ), false ); } if( newName != null ) { - player.addChatMessage( new TranslatableText( "chat.computercraft.wired_modem.peripheral_connected", + player.sendMessage( new TranslatableText( "chat.computercraft.wired_modem.peripheral_connected", CommandCopy.createCopyText( newName ) ), false ); } } diff --git a/src/main/java/dan200/computercraft/shared/peripheral/modem/wired/TileWiredModemFull.java b/src/main/java/dan200/computercraft/shared/peripheral/modem/wired/TileWiredModemFull.java index ce773f940..facdeef32 100644 --- a/src/main/java/dan200/computercraft/shared/peripheral/modem/wired/TileWiredModemFull.java +++ b/src/main/java/dan200/computercraft/shared/peripheral/modem/wired/TileWiredModemFull.java @@ -140,9 +140,9 @@ public class TileWiredModemFull extends TileGeneric implements IPeripheralTile */ @Override - public void invalidate() + public void markRemoved() { - super.invalidate(); + super.markRemoved(); doRemove(); } @@ -201,7 +201,7 @@ public class TileWiredModemFull extends TileGeneric implements IPeripheralTile base.append( CommandCopy.createCopyText( names.get( i ) ) ); } - player.addChatMessage( new TranslatableText( kind, base ), false ); + player.sendMessage( new TranslatableText( kind, base ), false ); } @Override @@ -231,9 +231,9 @@ public class TileWiredModemFull extends TileGeneric implements IPeripheralTile } @Override - public void validate() + public void cancelRemoval() { - super.validate(); + super.cancelRemoval(); TickScheduler.schedule( this ); } diff --git a/src/main/java/dan200/computercraft/shared/peripheral/modem/wired/WiredModemLocalPeripheral.java b/src/main/java/dan200/computercraft/shared/peripheral/modem/wired/WiredModemLocalPeripheral.java index 4f4b13833..82b99ddba 100644 --- a/src/main/java/dan200/computercraft/shared/peripheral/modem/wired/WiredModemLocalPeripheral.java +++ b/src/main/java/dan200/computercraft/shared/peripheral/modem/wired/WiredModemLocalPeripheral.java @@ -124,10 +124,10 @@ public final class WiredModemLocalPeripheral public void fromTag( @Nonnull CompoundTag tag, @Nonnull String suffix ) { - id = tag.containsKey( NBT_PERIPHERAL_ID + suffix, NBTUtil.TAG_ANY_NUMERIC ) + id = tag.contains( NBT_PERIPHERAL_ID + suffix, NBTUtil.TAG_ANY_NUMERIC ) ? tag.getInt( NBT_PERIPHERAL_ID + suffix ) : -1; - type = tag.containsKey( NBT_PERIPHERAL_TYPE + suffix, NBTUtil.TAG_STRING ) + type = tag.contains( NBT_PERIPHERAL_TYPE + suffix, NBTUtil.TAG_STRING ) ? tag.getString( NBT_PERIPHERAL_TYPE + suffix ) : null; } diff --git a/src/main/java/dan200/computercraft/shared/peripheral/modem/wireless/BlockWirelessModem.java b/src/main/java/dan200/computercraft/shared/peripheral/modem/wireless/BlockWirelessModem.java index 2ed0f54ba..43e715c85 100644 --- a/src/main/java/dan200/computercraft/shared/peripheral/modem/wireless/BlockWirelessModem.java +++ b/src/main/java/dan200/computercraft/shared/peripheral/modem/wireless/BlockWirelessModem.java @@ -12,10 +12,10 @@ import dan200.computercraft.shared.util.NamedBlockEntityType; import dan200.computercraft.shared.util.WaterloggableBlock; import net.minecraft.block.Block; import net.minecraft.block.BlockState; -import net.minecraft.entity.EntityContext; +import net.minecraft.block.ShapeContext; import net.minecraft.fluid.FluidState; import net.minecraft.item.ItemPlacementContext; -import net.minecraft.state.StateFactory; +import net.minecraft.state.StateManager; import net.minecraft.state.property.BooleanProperty; import net.minecraft.state.property.DirectionProperty; import net.minecraft.state.property.Properties; @@ -23,9 +23,8 @@ import net.minecraft.util.math.BlockPos; import net.minecraft.util.math.Direction; import net.minecraft.util.shape.VoxelShape; import net.minecraft.world.BlockView; -import net.minecraft.world.IWorld; -import net.minecraft.world.ViewableWorld; - +import net.minecraft.world.CollisionView; +import net.minecraft.world.WorldAccess; import javax.annotation.Nonnull; import javax.annotation.Nullable; @@ -37,14 +36,14 @@ public class BlockWirelessModem extends BlockGeneric implements WaterloggableBlo public BlockWirelessModem( Settings settings, NamedBlockEntityType type ) { super( settings, type ); - setDefaultState( getStateFactory().getDefaultState() + setDefaultState( getStateManager().getDefaultState() .with( FACING, Direction.NORTH ) .with( ON, false ) .with( WATERLOGGED, false ) ); } @Override - protected void appendProperties( StateFactory.Builder builder ) + protected void appendProperties( StateManager.Builder builder ) { builder.add( FACING, ON, WATERLOGGED ); } @@ -52,7 +51,7 @@ public class BlockWirelessModem extends BlockGeneric implements WaterloggableBlo @Nonnull @Override @Deprecated - public VoxelShape getOutlineShape( BlockState blockState, BlockView world, BlockPos pos, EntityContext position ) + public VoxelShape getOutlineShape( BlockState blockState, BlockView world, BlockPos pos, ShapeContext position ) { return ModemShapes.getBounds( blockState.get( FACING ) ); } @@ -68,7 +67,7 @@ public class BlockWirelessModem extends BlockGeneric implements WaterloggableBlo @Nonnull @Override @Deprecated - public BlockState getStateForNeighborUpdate( BlockState state, Direction side, BlockState otherState, IWorld world, BlockPos pos, BlockPos otherPos ) + public BlockState getStateForNeighborUpdate( BlockState state, Direction side, BlockState otherState, WorldAccess world, BlockPos pos, BlockPos otherPos ) { updateWaterloggedPostPlacement( state, world, pos ); return side == state.get( FACING ) && !state.canPlaceAt( world, pos ) @@ -78,7 +77,7 @@ public class BlockWirelessModem extends BlockGeneric implements WaterloggableBlo @Override @Deprecated - public boolean canPlaceAt( BlockState state, ViewableWorld world, BlockPos pos ) + public boolean canPlaceAt( BlockState state, CollisionView world, BlockPos pos ) { Direction facing = state.get( FACING ); BlockPos offsetPos = pos.offset( facing ); diff --git a/src/main/java/dan200/computercraft/shared/peripheral/modem/wireless/TileWirelessModem.java b/src/main/java/dan200/computercraft/shared/peripheral/modem/wireless/TileWirelessModem.java index c1ad2ef9a..01524bd56 100644 --- a/src/main/java/dan200/computercraft/shared/peripheral/modem/wireless/TileWirelessModem.java +++ b/src/main/java/dan200/computercraft/shared/peripheral/modem/wireless/TileWirelessModem.java @@ -84,9 +84,9 @@ public class TileWirelessModem extends TileGeneric implements IPeripheralTile } @Override - public void validate() + public void cancelRemoval() { - super.validate(); + super.cancelRemoval(); TickScheduler.schedule( this ); } diff --git a/src/main/java/dan200/computercraft/shared/peripheral/monitor/BlockMonitor.java b/src/main/java/dan200/computercraft/shared/peripheral/monitor/BlockMonitor.java index ca7c34bfb..e03edc3f6 100644 --- a/src/main/java/dan200/computercraft/shared/peripheral/monitor/BlockMonitor.java +++ b/src/main/java/dan200/computercraft/shared/peripheral/monitor/BlockMonitor.java @@ -10,13 +10,13 @@ import dan200.computercraft.shared.common.BlockGeneric; import dan200.computercraft.shared.common.TileGeneric; import dan200.computercraft.shared.util.NamedBlockEntityType; import net.minecraft.block.Block; -import net.minecraft.block.BlockRenderLayer; import net.minecraft.block.BlockState; import net.minecraft.block.entity.BlockEntity; +import net.minecraft.client.render.RenderLayer; import net.minecraft.entity.LivingEntity; import net.minecraft.item.ItemPlacementContext; import net.minecraft.item.ItemStack; -import net.minecraft.state.StateFactory; +import net.minecraft.state.StateManager; import net.minecraft.state.property.DirectionProperty; import net.minecraft.state.property.EnumProperty; import net.minecraft.state.property.Properties; @@ -38,20 +38,20 @@ public class BlockMonitor extends BlockGeneric public BlockMonitor( Settings settings, NamedBlockEntityType type ) { super( settings, type ); - setDefaultState( getStateFactory().getDefaultState() + setDefaultState( getStateManager().getDefaultState() .with( ORIENTATION, Direction.NORTH ) .with( FACING, Direction.NORTH ) .with( STATE, MonitorEdgeState.NONE ) ); } @Override - public BlockRenderLayer getRenderLayer() + public RenderLayer getRenderLayer() { - return BlockRenderLayer.CUTOUT; + return RenderLayer.CUTOUT; } @Override - protected void appendProperties( StateFactory.Builder builder ) + protected void appendProperties( StateManager.Builder builder ) { builder.add( ORIENTATION, FACING, STATE ); } diff --git a/src/main/java/dan200/computercraft/shared/peripheral/monitor/TileMonitor.java b/src/main/java/dan200/computercraft/shared/peripheral/monitor/TileMonitor.java index 8159bc44c..de642570e 100644 --- a/src/main/java/dan200/computercraft/shared/peripheral/monitor/TileMonitor.java +++ b/src/main/java/dan200/computercraft/shared/peripheral/monitor/TileMonitor.java @@ -76,9 +76,9 @@ public class TileMonitor extends TileGeneric implements IPeripheralTile } @Override - public void validate() + public void cancelRemoval() { - super.validate(); + super.cancelRemoval(); TickScheduler.schedule( this ); } @@ -92,9 +92,9 @@ public class TileMonitor extends TileGeneric implements IPeripheralTile } @Override - public void invalidate() + public void markRemoved() { - super.invalidate(); + super.markRemoved(); if( m_clientMonitor != null && m_xIndex == 0 && m_yIndex == 0 ) m_clientMonitor.destroy(); } diff --git a/src/main/java/dan200/computercraft/shared/peripheral/printer/BlockPrinter.java b/src/main/java/dan200/computercraft/shared/peripheral/printer/BlockPrinter.java index 36a632df8..ed7aff55d 100644 --- a/src/main/java/dan200/computercraft/shared/peripheral/printer/BlockPrinter.java +++ b/src/main/java/dan200/computercraft/shared/peripheral/printer/BlockPrinter.java @@ -13,7 +13,7 @@ import net.minecraft.block.entity.BlockEntity; import net.minecraft.entity.LivingEntity; import net.minecraft.item.ItemPlacementContext; import net.minecraft.item.ItemStack; -import net.minecraft.state.StateFactory; +import net.minecraft.state.StateManager; import net.minecraft.state.property.BooleanProperty; import net.minecraft.state.property.DirectionProperty; import net.minecraft.state.property.Properties; @@ -32,14 +32,14 @@ public class BlockPrinter extends BlockGeneric public BlockPrinter( Settings settings ) { super( settings, TilePrinter.FACTORY ); - setDefaultState( getStateFactory().getDefaultState() + setDefaultState( getStateManager().getDefaultState() .with( FACING, Direction.NORTH ) .with( TOP, false ) .with( BOTTOM, false ) ); } @Override - protected void appendProperties( StateFactory.Builder properties ) + protected void appendProperties( StateManager.Builder properties ) { super.appendProperties( properties ); properties.add( FACING, TOP, BOTTOM ); diff --git a/src/main/java/dan200/computercraft/shared/peripheral/printer/ContainerPrinter.java b/src/main/java/dan200/computercraft/shared/peripheral/printer/ContainerPrinter.java index 38b31a759..19558dacc 100644 --- a/src/main/java/dan200/computercraft/shared/peripheral/printer/ContainerPrinter.java +++ b/src/main/java/dan200/computercraft/shared/peripheral/printer/ContainerPrinter.java @@ -6,22 +6,21 @@ package dan200.computercraft.shared.peripheral.printer; -import net.minecraft.container.ArrayPropertyDelegate; -import net.minecraft.container.Container; -import net.minecraft.container.PropertyDelegate; -import net.minecraft.container.Slot; import net.minecraft.entity.player.PlayerEntity; import net.minecraft.entity.player.PlayerInventory; -import net.minecraft.inventory.BasicInventory; import net.minecraft.inventory.Inventory; +import net.minecraft.inventory.SimpleInventory; import net.minecraft.item.DyeItem; import net.minecraft.item.ItemStack; - +import net.minecraft.screen.ArrayPropertyDelegate; +import net.minecraft.screen.PropertyDelegate; +import net.minecraft.screen.ScreenHandler; +import net.minecraft.screen.slot.Slot; import javax.annotation.Nonnull; import static dan200.computercraft.shared.peripheral.printer.TilePrinter.PROPERTY_PRINTING; -public class ContainerPrinter extends Container +public class ContainerPrinter extends ScreenHandler { private final Inventory m_printer; private final PropertyDelegate properties; @@ -33,7 +32,7 @@ public class ContainerPrinter extends Container public ContainerPrinter( int id, PlayerInventory player ) { - this( id, player, new BasicInventory( TilePrinter.INVENTORY_SIZE ), new ArrayPropertyDelegate( TilePrinter.PROPERTY_SIZE ) ); + this( id, player, new SimpleInventory( TilePrinter.INVENTORY_SIZE ), new ArrayPropertyDelegate( TilePrinter.PROPERTY_SIZE ) ); } public ContainerPrinter( int id, PlayerInventory playerInventory, Inventory printer, PropertyDelegate printerInfo ) @@ -74,14 +73,14 @@ public class ContainerPrinter extends Container @Override public boolean canUse( @Nonnull PlayerEntity player ) { - return m_printer.canPlayerUseInv( player ); + return m_printer.canPlayerUse( player ); } @Nonnull @Override public ItemStack transferSlot( PlayerEntity player, int index ) { - Slot slot = slotList.get( index ); + Slot slot = slots.get( index ); if( slot == null || !slot.hasStack() ) return ItemStack.EMPTY; ItemStack stack = slot.getStack(); ItemStack result = stack.copy(); diff --git a/src/main/java/dan200/computercraft/shared/peripheral/printer/TilePrinter.java b/src/main/java/dan200/computercraft/shared/peripheral/printer/TilePrinter.java index 3a389a14a..a863fe4a3 100644 --- a/src/main/java/dan200/computercraft/shared/peripheral/printer/TilePrinter.java +++ b/src/main/java/dan200/computercraft/shared/peripheral/printer/TilePrinter.java @@ -25,6 +25,7 @@ import net.minecraft.nbt.CompoundTag; import net.minecraft.text.LiteralText; import net.minecraft.text.Text; import net.minecraft.util.*; +import net.minecraft.util.collection.DefaultedList; import net.minecraft.util.hit.BlockHitResult; import net.minecraft.util.math.BlockPos; import net.minecraft.util.math.Direction; @@ -86,7 +87,7 @@ public final class TilePrinter extends TileGeneric implements DefaultSidedInvent { super.fromTag( nbt ); - customName = nbt.containsKey( NBT_NAME ) ? LiteralText.Serializer.fromJson( nbt.getString( NBT_NAME ) ) : null; + customName = nbt.contains( NBT_NAME ) ? LiteralText.Serializer.fromJson( nbt.getString( NBT_NAME ) ) : null; // Read page synchronized( m_page ) @@ -137,7 +138,7 @@ public final class TilePrinter extends TileGeneric implements DefaultSidedInvent public void readDescription( @Nonnull CompoundTag nbt ) { super.readDescription( nbt ); - customName = nbt.containsKey( NBT_NAME ) ? LiteralText.Serializer.fromJson( nbt.getString( NBT_NAME ) ) : null; + customName = nbt.contains( NBT_NAME ) ? LiteralText.Serializer.fromJson( nbt.getString( NBT_NAME ) ) : null; updateBlock(); } @@ -148,13 +149,13 @@ public final class TilePrinter extends TileGeneric implements DefaultSidedInvent // Inventory implementation @Override - public int getInvSize() + public int size() { return m_inventory.size(); } @Override - public boolean isInvEmpty() + public boolean isEmpty() { for( ItemStack stack : m_inventory ) { @@ -165,14 +166,14 @@ public final class TilePrinter extends TileGeneric implements DefaultSidedInvent @Nonnull @Override - public ItemStack getInvStack( int i ) + public ItemStack getStack( int i ) { return m_inventory.get( i ); } @Nonnull @Override - public ItemStack removeInvStack( int i ) + public ItemStack removeStack( int i ) { synchronized( m_inventory ) { @@ -186,7 +187,7 @@ public final class TilePrinter extends TileGeneric implements DefaultSidedInvent @Nonnull @Override - public ItemStack takeInvStack( int i, int j ) + public ItemStack removeStack( int i, int j ) { synchronized( m_inventory ) { @@ -213,7 +214,7 @@ public final class TilePrinter extends TileGeneric implements DefaultSidedInvent } @Override - public void setInvStack( int i, @Nonnull ItemStack stack ) + public void setStack( int i, @Nonnull ItemStack stack ) { synchronized( m_inventory ) { @@ -235,7 +236,7 @@ public final class TilePrinter extends TileGeneric implements DefaultSidedInvent } @Override - public boolean isValidInvStack( int slot, @Nonnull ItemStack stack ) + public boolean isValid( int slot, @Nonnull ItemStack stack ) { if( slot == 0 ) { @@ -252,7 +253,7 @@ public final class TilePrinter extends TileGeneric implements DefaultSidedInvent } @Override - public boolean canPlayerUseInv( PlayerEntity playerEntity ) + public boolean canPlayerUse( PlayerEntity playerEntity ) { return isUsable( playerEntity, false ); } @@ -260,7 +261,7 @@ public final class TilePrinter extends TileGeneric implements DefaultSidedInvent // ISidedInventory implementation @Override - public int[] getInvAvailableSlots( @Nonnull Direction side ) + public int[] getAvailableSlots( @Nonnull Direction side ) { switch( side ) { @@ -437,7 +438,7 @@ public final class TilePrinter extends TileGeneric implements DefaultSidedInvent { if( m_inventory.get( slot ).isEmpty() ) { - setInvStack( slot, stack ); + setStack( slot, stack ); m_printing = false; return true; } @@ -457,7 +458,7 @@ public final class TilePrinter extends TileGeneric implements DefaultSidedInvent if( !stack.isEmpty() ) { // Remove the stack from the inventory - setInvStack( i, ItemStack.EMPTY ); + setStack( i, ItemStack.EMPTY ); // Spawn the item in the world BlockPos pos = getPos(); @@ -500,7 +501,7 @@ public final class TilePrinter extends TileGeneric implements DefaultSidedInvent private void updateBlockState( boolean top, boolean bottom ) { - if( invalid ) return; + if( removed ) return; BlockState state = getCachedState(); if( state.get( BlockPrinter.TOP ) == top & state.get( BlockPrinter.BOTTOM ) == bottom ) return; diff --git a/src/main/java/dan200/computercraft/shared/peripheral/speaker/BlockSpeaker.java b/src/main/java/dan200/computercraft/shared/peripheral/speaker/BlockSpeaker.java index c26c27f59..790f91c1d 100644 --- a/src/main/java/dan200/computercraft/shared/peripheral/speaker/BlockSpeaker.java +++ b/src/main/java/dan200/computercraft/shared/peripheral/speaker/BlockSpeaker.java @@ -10,7 +10,7 @@ import dan200.computercraft.shared.common.BlockGeneric; import net.minecraft.block.Block; import net.minecraft.block.BlockState; import net.minecraft.item.ItemPlacementContext; -import net.minecraft.state.StateFactory; +import net.minecraft.state.StateManager; import net.minecraft.state.property.DirectionProperty; import net.minecraft.state.property.Properties; import net.minecraft.util.math.Direction; @@ -24,12 +24,12 @@ public class BlockSpeaker extends BlockGeneric public BlockSpeaker( Settings settings ) { super( settings, TileSpeaker.FACTORY ); - setDefaultState( getStateFactory().getDefaultState() + setDefaultState( getStateManager().getDefaultState() .with( FACING, Direction.NORTH ) ); } @Override - protected void appendProperties( StateFactory.Builder properties ) + protected void appendProperties( StateManager.Builder properties ) { properties.add( FACING ); } diff --git a/src/main/java/dan200/computercraft/shared/peripheral/speaker/SpeakerPeripheral.java b/src/main/java/dan200/computercraft/shared/peripheral/speaker/SpeakerPeripheral.java index 38c6d16a6..d5e094ab3 100644 --- a/src/main/java/dan200/computercraft/shared/peripheral/speaker/SpeakerPeripheral.java +++ b/src/main/java/dan200/computercraft/shared/peripheral/speaker/SpeakerPeripheral.java @@ -12,7 +12,7 @@ import dan200.computercraft.api.lua.LuaException; import dan200.computercraft.api.peripheral.IComputerAccess; import dan200.computercraft.api.peripheral.IPeripheral; import net.minecraft.block.enums.Instrument; -import net.minecraft.client.network.packet.PlaySoundIdS2CPacket; +import net.minecraft.network.packet.s2c.play.PlaySoundIdS2CPacket; import net.minecraft.server.MinecraftServer; import net.minecraft.sound.SoundCategory; import net.minecraft.util.Identifier; diff --git a/src/main/java/dan200/computercraft/shared/pocket/apis/PocketAPI.java b/src/main/java/dan200/computercraft/shared/pocket/apis/PocketAPI.java index 0a82481a1..768e072d1 100644 --- a/src/main/java/dan200/computercraft/shared/pocket/apis/PocketAPI.java +++ b/src/main/java/dan200/computercraft/shared/pocket/apis/PocketAPI.java @@ -19,8 +19,7 @@ import net.minecraft.entity.Entity; import net.minecraft.entity.player.PlayerEntity; import net.minecraft.entity.player.PlayerInventory; import net.minecraft.item.ItemStack; -import net.minecraft.util.DefaultedList; - +import net.minecraft.util.collection.DefaultedList; import javax.annotation.Nonnull; public class PocketAPI implements ILuaAPI diff --git a/src/main/java/dan200/computercraft/shared/pocket/core/PocketServerComputer.java b/src/main/java/dan200/computercraft/shared/pocket/core/PocketServerComputer.java index f9d2669be..64a9a9698 100644 --- a/src/main/java/dan200/computercraft/shared/pocket/core/PocketServerComputer.java +++ b/src/main/java/dan200/computercraft/shared/pocket/core/PocketServerComputer.java @@ -85,7 +85,7 @@ public class PocketServerComputer extends ServerComputer implements IPocketAcces public int getLight() { CompoundTag tag = getUserData(); - return tag.containsKey( NBT_LIGHT, NBTUtil.TAG_ANY_NUMERIC ) ? tag.getInt( NBT_LIGHT ) : -1; + return tag.contains( NBT_LIGHT, NBTUtil.TAG_ANY_NUMERIC ) ? tag.getInt( NBT_LIGHT ) : -1; } @Override @@ -94,13 +94,13 @@ public class PocketServerComputer extends ServerComputer implements IPocketAcces CompoundTag tag = getUserData(); if( colour >= 0 && colour <= 0xFFFFFF ) { - if( !tag.containsKey( NBT_LIGHT, NBTUtil.TAG_ANY_NUMERIC ) || tag.getInt( NBT_LIGHT ) != colour ) + if( !tag.contains( NBT_LIGHT, NBTUtil.TAG_ANY_NUMERIC ) || tag.getInt( NBT_LIGHT ) != colour ) { tag.putInt( NBT_LIGHT, colour ); updateUserData(); } } - else if( tag.containsKey( NBT_LIGHT, NBTUtil.TAG_ANY_NUMERIC ) ) + else if( tag.contains( NBT_LIGHT, NBTUtil.TAG_ANY_NUMERIC ) ) { tag.remove( NBT_LIGHT ); updateUserData(); diff --git a/src/main/java/dan200/computercraft/shared/pocket/items/ItemPocketComputer.java b/src/main/java/dan200/computercraft/shared/pocket/items/ItemPocketComputer.java index c8fd9d649..57728acca 100644 --- a/src/main/java/dan200/computercraft/shared/pocket/items/ItemPocketComputer.java +++ b/src/main/java/dan200/computercraft/shared/pocket/items/ItemPocketComputer.java @@ -25,19 +25,20 @@ import dan200.computercraft.shared.pocket.apis.PocketAPI; import dan200.computercraft.shared.pocket.core.PocketServerComputer; import net.fabricmc.api.EnvType; import net.fabricmc.api.Environment; +import net.minecraft.client.item.ModelPredicateProvider; import net.minecraft.client.item.TooltipContext; import net.minecraft.entity.Entity; import net.minecraft.entity.player.PlayerEntity; import net.minecraft.inventory.Inventory; import net.minecraft.item.Item; import net.minecraft.item.ItemGroup; -import net.minecraft.item.ItemPropertyGetter; import net.minecraft.item.ItemStack; import net.minecraft.nbt.CompoundTag; import net.minecraft.text.LiteralText; import net.minecraft.text.Text; import net.minecraft.text.TranslatableText; import net.minecraft.util.*; +import net.minecraft.util.collection.DefaultedList; import net.minecraft.world.World; import javax.annotation.Nonnull; @@ -319,7 +320,7 @@ public class ItemPocketComputer extends Item implements IComputerItem, IMedia, I private static int getInstanceID( @Nonnull ItemStack stack ) { CompoundTag nbt = stack.getTag(); - return nbt != null && nbt.containsKey( NBT_INSTANCE ) ? nbt.getInt( NBT_INSTANCE ) : -1; + return nbt != null && nbt.contains( NBT_INSTANCE ) ? nbt.getInt( NBT_INSTANCE ) : -1; } private static void setInstanceID( @Nonnull ItemStack stack, int instanceID ) @@ -330,7 +331,7 @@ public class ItemPocketComputer extends Item implements IComputerItem, IMedia, I private static int getSessionID( @Nonnull ItemStack stack ) { CompoundTag nbt = stack.getTag(); - return nbt != null && nbt.containsKey( NBT_SESSION ) ? nbt.getInt( NBT_SESSION ) : -1; + return nbt != null && nbt.contains( NBT_SESSION ) ? nbt.getInt( NBT_SESSION ) : -1; } private static void setSessionID( @Nonnull ItemStack stack, int sessionID ) @@ -352,7 +353,7 @@ public class ItemPocketComputer extends Item implements IComputerItem, IMedia, I if( computer != null && computer.isOn() ) { CompoundTag computerNBT = computer.getUserData(); - if( computerNBT != null && computerNBT.containsKey( NBT_LIGHT ) ) + if( computerNBT != null && computerNBT.contains( NBT_LIGHT ) ) { return computerNBT.getInt( NBT_LIGHT ); } @@ -363,7 +364,7 @@ public class ItemPocketComputer extends Item implements IComputerItem, IMedia, I public static IPocketUpgrade getUpgrade( @Nonnull ItemStack stack ) { CompoundTag compound = stack.getTag(); - return compound != null && compound.containsKey( NBT_UPGRADE ) + return compound != null && compound.contains( NBT_UPGRADE ) ? PocketUpgrades.get( compound.getString( NBT_UPGRADE ) ) : null; } @@ -389,6 +390,6 @@ public class ItemPocketComputer extends Item implements IComputerItem, IMedia, I return stack.getOrCreateSubTag( NBT_UPGRADE_INFO ); } - private static final ItemPropertyGetter COMPUTER_STATE = ( stack, world, player ) -> getState( stack ).ordinal(); - private static final ItemPropertyGetter COMPUTER_COLOURED = ( stack, world, player ) -> IColouredItem.getColourBasic( stack ) != -1 ? 1 : 0; + private static final ModelPredicateProvider COMPUTER_STATE = ( stack, world, player ) -> getState( stack ).ordinal(); + private static final ModelPredicateProvider COMPUTER_COLOURED = ( stack, world, player ) -> IColouredItem.getColourBasic( stack ) != -1 ? 1 : 0; } diff --git a/src/main/java/dan200/computercraft/shared/pocket/recipes/PocketComputerUpgradeRecipe.java b/src/main/java/dan200/computercraft/shared/pocket/recipes/PocketComputerUpgradeRecipe.java index 3ec201c50..06e799c0d 100644 --- a/src/main/java/dan200/computercraft/shared/pocket/recipes/PocketComputerUpgradeRecipe.java +++ b/src/main/java/dan200/computercraft/shared/pocket/recipes/PocketComputerUpgradeRecipe.java @@ -60,7 +60,7 @@ public final class PocketComputerUpgradeRecipe extends SpecialCraftingRecipe { for( int x = 0; x < inventory.getWidth(); x++ ) { - ItemStack item = inventory.getInvStack( x + y * inventory.getWidth() ); + ItemStack item = inventory.getStack( x + y * inventory.getWidth() ); if( !item.isEmpty() && item.getItem() instanceof ItemPocketComputer ) { computer = item; @@ -82,7 +82,7 @@ public final class PocketComputerUpgradeRecipe extends SpecialCraftingRecipe { for( int x = 0; x < inventory.getWidth(); x++ ) { - ItemStack item = inventory.getInvStack( x + y * inventory.getWidth() ); + ItemStack item = inventory.getStack( x + y * inventory.getWidth() ); if( x == computerX && y == computerY ) continue; if( x == computerX && y == computerY - 1 ) diff --git a/src/main/java/dan200/computercraft/shared/proxy/ComputerCraftProxyCommon.java b/src/main/java/dan200/computercraft/shared/proxy/ComputerCraftProxyCommon.java index f8426adba..c75a292d7 100644 --- a/src/main/java/dan200/computercraft/shared/proxy/ComputerCraftProxyCommon.java +++ b/src/main/java/dan200/computercraft/shared/proxy/ComputerCraftProxyCommon.java @@ -56,7 +56,7 @@ public class ComputerCraftProxyCommon NetworkHandler.setup(); Registry.registerBlocks( net.minecraft.util.registry.Registry.BLOCK ); - Registry.registerTileEntities( (MutableRegistry>) net.minecraft.util.registry.Registry.BLOCK_ENTITY ); + Registry.registerTileEntities( (MutableRegistry>) net.minecraft.util.registry.Registry.BLOCK_ENTITY_TYPE ); Registry.registerItems( net.minecraft.util.registry.Registry.ITEM ); Registry.registerRecipes( (MutableRegistry>) net.minecraft.util.registry.Registry.RECIPE_SERIALIZER ); diff --git a/src/main/java/dan200/computercraft/shared/turtle/apis/TurtleAPI.java b/src/main/java/dan200/computercraft/shared/turtle/apis/TurtleAPI.java index e5e0ed99a..ae0b1996a 100644 --- a/src/main/java/dan200/computercraft/shared/turtle/apis/TurtleAPI.java +++ b/src/main/java/dan200/computercraft/shared/turtle/apis/TurtleAPI.java @@ -221,14 +221,14 @@ public class TurtleAPI implements ILuaAPI { // getItemCount int slot = parseOptionalSlotNumber( args, 0, m_turtle.getSelectedSlot() ); - ItemStack stack = m_turtle.getInventory().getInvStack( slot ); + ItemStack stack = m_turtle.getInventory().getStack( slot ); return new Object[] { stack.getCount() }; } case 15: { // getItemSpace int slot = parseOptionalSlotNumber( args, 0, m_turtle.getSelectedSlot() ); - ItemStack stack = m_turtle.getInventory().getInvStack( slot ); + ItemStack stack = m_turtle.getInventory().getStack( slot ); return new Object[] { stack.isEmpty() ? 64 : Math.min( stack.getMaxCount(), 64 ) - stack.getCount() }; } case 16: // detect @@ -341,7 +341,7 @@ public class TurtleAPI implements ILuaAPI { // getItemDetail int slot = parseOptionalSlotNumber( args, 0, m_turtle.getSelectedSlot() ); - ItemStack stack = m_turtle.getInventory().getInvStack( slot ); + ItemStack stack = m_turtle.getInventory().getStack( slot ); if( stack.isEmpty() ) return new Object[] { null }; Item item = stack.getItem(); diff --git a/src/main/java/dan200/computercraft/shared/turtle/blocks/BlockTurtle.java b/src/main/java/dan200/computercraft/shared/turtle/blocks/BlockTurtle.java index a3bb81757..2f5f32f51 100644 --- a/src/main/java/dan200/computercraft/shared/turtle/blocks/BlockTurtle.java +++ b/src/main/java/dan200/computercraft/shared/turtle/blocks/BlockTurtle.java @@ -18,14 +18,14 @@ import dan200.computercraft.shared.util.WaterloggableBlock; import net.minecraft.block.Block; import net.minecraft.block.BlockRenderType; import net.minecraft.block.BlockState; +import net.minecraft.block.ShapeContext; import net.minecraft.block.entity.BlockEntity; -import net.minecraft.entity.EntityContext; import net.minecraft.entity.LivingEntity; import net.minecraft.entity.player.PlayerEntity; import net.minecraft.fluid.FluidState; import net.minecraft.item.ItemPlacementContext; import net.minecraft.item.ItemStack; -import net.minecraft.state.StateFactory; +import net.minecraft.state.StateManager; import net.minecraft.state.property.DirectionProperty; import net.minecraft.state.property.Properties; import net.minecraft.util.Identifier; @@ -35,9 +35,8 @@ import net.minecraft.util.math.Vec3d; import net.minecraft.util.shape.VoxelShape; import net.minecraft.util.shape.VoxelShapes; import net.minecraft.world.BlockView; -import net.minecraft.world.IWorld; import net.minecraft.world.World; - +import net.minecraft.world.WorldAccess; import javax.annotation.Nonnull; import javax.annotation.Nullable; @@ -53,14 +52,14 @@ public class BlockTurtle extends BlockComputerBase implements Waterl public BlockTurtle( Settings settings, ComputerFamily family, NamedBlockEntityType type ) { super( settings, family, type ); - setDefaultState( getStateFactory().getDefaultState() + setDefaultState( getStateManager().getDefaultState() .with( FACING, Direction.NORTH ) .with( WATERLOGGED, false ) ); } @Override - protected void appendProperties( StateFactory.Builder builder ) + protected void appendProperties( StateManager.Builder builder ) { builder.add( FACING, WATERLOGGED ); } @@ -76,7 +75,7 @@ public class BlockTurtle extends BlockComputerBase implements Waterl @Nonnull @Override @Deprecated - public VoxelShape getOutlineShape( BlockState state, BlockView world, BlockPos pos, EntityContext position ) + public VoxelShape getOutlineShape( BlockState state, BlockView world, BlockPos pos, ShapeContext position ) { BlockEntity tile = world.getBlockEntity( pos ); Vec3d offset = tile instanceof TileTurtle ? ((TileTurtle) tile).getRenderOffset( 1.0f ) : Vec3d.ZERO; @@ -103,7 +102,7 @@ public class BlockTurtle extends BlockComputerBase implements Waterl @Nonnull @Override @Deprecated - public BlockState getStateForNeighborUpdate( @Nonnull BlockState state, Direction side, BlockState otherState, IWorld world, BlockPos pos, BlockPos otherPos ) + public BlockState getStateForNeighborUpdate( @Nonnull BlockState state, Direction side, BlockState otherState, WorldAccess world, BlockPos pos, BlockPos otherPos ) { updateWaterloggedPostPlacement( state, world, pos ); return state; diff --git a/src/main/java/dan200/computercraft/shared/turtle/blocks/TileTurtle.java b/src/main/java/dan200/computercraft/shared/turtle/blocks/TileTurtle.java index dc21a6fcc..196d79632 100644 --- a/src/main/java/dan200/computercraft/shared/turtle/blocks/TileTurtle.java +++ b/src/main/java/dan200/computercraft/shared/turtle/blocks/TileTurtle.java @@ -22,6 +22,7 @@ import dan200.computercraft.shared.computer.core.ComputerState; import dan200.computercraft.shared.computer.core.ServerComputer; import dan200.computercraft.shared.network.Containers; import dan200.computercraft.shared.turtle.apis.TurtleAPI; +import dan200.computercraft.shared.turtle.blocks.TileTurtle.MoveState; import dan200.computercraft.shared.turtle.core.TurtleBrain; import dan200.computercraft.shared.util.*; import net.minecraft.block.entity.BlockEntityType; @@ -32,6 +33,7 @@ import net.minecraft.item.Items; import net.minecraft.nbt.CompoundTag; import net.minecraft.nbt.ListTag; import net.minecraft.util.*; +import net.minecraft.util.collection.DefaultedList; import net.minecraft.util.hit.BlockHitResult; import net.minecraft.util.math.BlockPos; import net.minecraft.util.math.Direction; @@ -118,10 +120,10 @@ public class TileTurtle extends TileComputerBase implements ITurtleTile, Default // Drop contents if( !getWorld().isClient ) { - int size = getInvSize(); + int size = size(); for( int i = 0; i < size; i++ ) { - ItemStack stack = getInvStack( i ); + ItemStack stack = getStack( i ); if( !stack.isEmpty() ) { WorldUtil.dropItemStack( stack, getWorld(), getPos() ); @@ -226,9 +228,9 @@ public class TileTurtle extends TileComputerBase implements ITurtleTile, Default if( computer != null ) computer.queueEvent( "turtle_inventory" ); m_inventoryChanged = false; - for( int n = 0; n < getInvSize(); n++ ) + for( int n = 0; n < size(); n++ ) { - m_previousInventory.set( n, InventoryUtil.copyItem( getInvStack( n ) ) ); + m_previousInventory.set( n, InventoryUtil.copyItem( getStack( n ) ) ); } } } @@ -273,9 +275,9 @@ public class TileTurtle extends TileComputerBase implements ITurtleTile, Default m_previousInventory = DefaultedList.ofSize( INVENTORY_SIZE, ItemStack.EMPTY ); for( int i = 0; i < nbttaglist.size(); i++ ) { - CompoundTag tag = nbttaglist.getCompoundTag( i ); + CompoundTag tag = nbttaglist.getCompound( i ); int slot = tag.getByte( "Slot" ) & 0xff; - if( slot < getInvSize() ) + if( slot < size() ) { m_inventory.set( slot, ItemStack.fromTag( tag ) ); m_previousInventory.set( slot, InventoryUtil.copyItem( m_inventory.get( slot ) ) ); @@ -386,13 +388,13 @@ public class TileTurtle extends TileComputerBase implements ITurtleTile, Default // IInventory @Override - public int getInvSize() + public int size() { return INVENTORY_SIZE; } @Override - public boolean isInvEmpty() + public boolean isEmpty() { for( ItemStack stack : m_inventory ) { @@ -403,7 +405,7 @@ public class TileTurtle extends TileComputerBase implements ITurtleTile, Default @Nonnull @Override - public ItemStack getInvStack( int slot ) + public ItemStack getStack( int slot ) { if( slot >= 0 && slot < INVENTORY_SIZE ) { @@ -417,19 +419,19 @@ public class TileTurtle extends TileComputerBase implements ITurtleTile, Default @Nonnull @Override - public ItemStack removeInvStack( int slot ) + public ItemStack removeStack( int slot ) { synchronized( m_inventory ) { - ItemStack result = getInvStack( slot ); - setInvStack( slot, ItemStack.EMPTY ); + ItemStack result = getStack( slot ); + setStack( slot, ItemStack.EMPTY ); return result; } } @Nonnull @Override - public ItemStack takeInvStack( int slot, int count ) + public ItemStack removeStack( int slot, int count ) { if( count == 0 ) { @@ -438,7 +440,7 @@ public class TileTurtle extends TileComputerBase implements ITurtleTile, Default synchronized( m_inventory ) { - ItemStack stack = getInvStack( slot ); + ItemStack stack = getStack( slot ); if( stack.isEmpty() ) { return ItemStack.EMPTY; @@ -446,7 +448,7 @@ public class TileTurtle extends TileComputerBase implements ITurtleTile, Default if( stack.getCount() <= count ) { - setInvStack( slot, ItemStack.EMPTY ); + setStack( slot, ItemStack.EMPTY ); return stack; } @@ -457,7 +459,7 @@ public class TileTurtle extends TileComputerBase implements ITurtleTile, Default } @Override - public void setInvStack( int i, @Nonnull ItemStack stack ) + public void setStack( int i, @Nonnull ItemStack stack ) { if( i >= 0 && i < INVENTORY_SIZE ) { @@ -501,9 +503,9 @@ public class TileTurtle extends TileComputerBase implements ITurtleTile, Default { if( !m_inventoryChanged ) { - for( int n = 0; n < getInvSize(); n++ ) + for( int n = 0; n < size(); n++ ) { - if( !ItemStack.areEqualIgnoreDamage( getInvStack( n ), m_previousInventory.get( n ) ) ) + if( !ItemStack.areEqual( getStack( n ), m_previousInventory.get( n ) ) ) { m_inventoryChanged = true; break; @@ -514,7 +516,7 @@ public class TileTurtle extends TileComputerBase implements ITurtleTile, Default } @Override - public boolean canPlayerUseInv( @Nonnull PlayerEntity player ) + public boolean canPlayerUse( @Nonnull PlayerEntity player ) { return isUsable( player, false ); } diff --git a/src/main/java/dan200/computercraft/shared/turtle/core/TurtleBrain.java b/src/main/java/dan200/computercraft/shared/turtle/core/TurtleBrain.java index 5e46d7b17..5c1353eb0 100644 --- a/src/main/java/dan200/computercraft/shared/turtle/core/TurtleBrain.java +++ b/src/main/java/dan200/computercraft/shared/turtle/core/TurtleBrain.java @@ -156,23 +156,23 @@ public class TurtleBrain implements ITurtleAccess private void readCommon( CompoundTag nbt ) { // Read fields - m_colourHex = nbt.containsKey( NBT_COLOUR ) ? nbt.getInt( NBT_COLOUR ) : -1; - m_fuelLevel = nbt.containsKey( NBT_FUEL ) ? nbt.getInt( NBT_FUEL ) : 0; - m_overlay = nbt.containsKey( NBT_OVERLAY ) ? new Identifier( nbt.getString( NBT_OVERLAY ) ) : null; + m_colourHex = nbt.contains( NBT_COLOUR ) ? nbt.getInt( NBT_COLOUR ) : -1; + m_fuelLevel = nbt.contains( NBT_FUEL ) ? nbt.getInt( NBT_FUEL ) : 0; + m_overlay = nbt.contains( NBT_OVERLAY ) ? new Identifier( nbt.getString( NBT_OVERLAY ) ) : null; // Read upgrades - setUpgrade( TurtleSide.Left, nbt.containsKey( NBT_LEFT_UPGRADE ) ? TurtleUpgrades.get( nbt.getString( NBT_LEFT_UPGRADE ) ) : null ); - setUpgrade( TurtleSide.Right, nbt.containsKey( NBT_RIGHT_UPGRADE ) ? TurtleUpgrades.get( nbt.getString( NBT_RIGHT_UPGRADE ) ) : null ); + setUpgrade( TurtleSide.Left, nbt.contains( NBT_LEFT_UPGRADE ) ? TurtleUpgrades.get( nbt.getString( NBT_LEFT_UPGRADE ) ) : null ); + setUpgrade( TurtleSide.Right, nbt.contains( NBT_RIGHT_UPGRADE ) ? TurtleUpgrades.get( nbt.getString( NBT_RIGHT_UPGRADE ) ) : null ); // NBT m_upgradeNBTData.clear(); - if( nbt.containsKey( NBT_LEFT_UPGRADE_DATA ) ) + if( nbt.contains( NBT_LEFT_UPGRADE_DATA ) ) { - m_upgradeNBTData.put( TurtleSide.Left, nbt.getCompound( NBT_LEFT_UPGRADE_DATA ).method_10553() ); + m_upgradeNBTData.put( TurtleSide.Left, nbt.getCompound( NBT_LEFT_UPGRADE_DATA ).copy() ); } - if( nbt.containsKey( NBT_RIGHT_UPGRADE_DATA ) ) + if( nbt.contains( NBT_RIGHT_UPGRADE_DATA ) ) { - m_upgradeNBTData.put( TurtleSide.Right, nbt.getCompound( NBT_RIGHT_UPGRADE_DATA ).method_10553() ); + m_upgradeNBTData.put( TurtleSide.Right, nbt.getCompound( NBT_RIGHT_UPGRADE_DATA ).copy() ); } } @@ -207,7 +207,7 @@ public class TurtleBrain implements ITurtleAccess m_selectedSlot = nbt.getInt( NBT_SLOT ); // Read owner - if( nbt.containsKey( "Owner", NBTUtil.TAG_COMPOUND ) ) + if( nbt.contains( "Owner", NBTUtil.TAG_COMPOUND ) ) { CompoundTag owner = nbt.getCompound( "Owner" ); m_owningPlayer = new GameProfile( @@ -315,7 +315,7 @@ public class TurtleBrain implements ITurtleAccess // We only mark this as waterlogged when travelling into a source block. This prevents us from spreading // fluid by creating a new source when moving into a block, causing the next block to be almost full and // then moving into that. - .with( WATERLOGGED, existingFluid.matches( FluidTags.WATER ) && existingFluid.isStill() ); + .with( WATERLOGGED, existingFluid.isIn( FluidTags.WATER ) && existingFluid.isStill() ); oldOwner.notifyMoveStart(); @@ -332,14 +332,14 @@ public class TurtleBrain implements ITurtleAccess { // Copy the old turtle state into the new turtle TileTurtle newTurtle = (TileTurtle) newTile; - newTurtle.setWorld( world ); + newTurtle.setLocation( world ); newTurtle.setPos( pos ); newTurtle.transferStateFrom( oldOwner ); newTurtle.createServerComputer().setWorld( world ); newTurtle.createServerComputer().setPosition( pos ); // Remove the old turtle - oldWorld.clearBlockState( oldPos, false ); + oldWorld.removeBlock( oldPos, false ); // Make sure everybody knows about it newTurtle.updateBlock(); @@ -350,7 +350,7 @@ public class TurtleBrain implements ITurtleAccess } // Something went wrong, remove the newly created turtle - world.clearBlockState( pos, false ); + world.removeBlock( pos, false ); } } finally @@ -427,7 +427,7 @@ public class TurtleBrain implements ITurtleAccess { if( getWorld().isClient ) throw new UnsupportedOperationException( "Cannot set the slot on the client" ); - if( slot >= 0 && slot < m_owner.getInvSize() ) + if( slot >= 0 && slot < m_owner.size() ) { m_selectedSlot = slot; m_owner.onTileEntityChange(); diff --git a/src/main/java/dan200/computercraft/shared/turtle/core/TurtleCompareCommand.java b/src/main/java/dan200/computercraft/shared/turtle/core/TurtleCompareCommand.java index 4138aeba0..4dfbcb2d2 100644 --- a/src/main/java/dan200/computercraft/shared/turtle/core/TurtleCompareCommand.java +++ b/src/main/java/dan200/computercraft/shared/turtle/core/TurtleCompareCommand.java @@ -37,7 +37,7 @@ public class TurtleCompareCommand implements ITurtleCommand Direction direction = m_direction.toWorldDir( turtle ); // Get currently selected stack - ItemStack selectedStack = turtle.getInventory().getInvStack( turtle.getSelectedSlot() ); + ItemStack selectedStack = turtle.getInventory().getStack( turtle.getSelectedSlot() ); // Get stack representing thing in front World world = turtle.getWorld(); diff --git a/src/main/java/dan200/computercraft/shared/turtle/core/TurtleCompareToCommand.java b/src/main/java/dan200/computercraft/shared/turtle/core/TurtleCompareToCommand.java index fe029ca23..a91d5b59f 100644 --- a/src/main/java/dan200/computercraft/shared/turtle/core/TurtleCompareToCommand.java +++ b/src/main/java/dan200/computercraft/shared/turtle/core/TurtleCompareToCommand.java @@ -27,8 +27,8 @@ public class TurtleCompareToCommand implements ITurtleCommand @Override public TurtleCommandResult execute( @Nonnull ITurtleAccess turtle ) { - ItemStack selectedStack = turtle.getInventory().getInvStack( turtle.getSelectedSlot() ); - ItemStack stack = turtle.getInventory().getInvStack( m_slot ); + ItemStack selectedStack = turtle.getInventory().getStack( turtle.getSelectedSlot() ); + ItemStack stack = turtle.getInventory().getStack( m_slot ); if( InventoryUtil.areItemsStackable( selectedStack, stack ) ) { return TurtleCommandResult.success(); diff --git a/src/main/java/dan200/computercraft/shared/turtle/core/TurtleDropCommand.java b/src/main/java/dan200/computercraft/shared/turtle/core/TurtleDropCommand.java index 3f0ecc0a7..f4bf1dd64 100644 --- a/src/main/java/dan200/computercraft/shared/turtle/core/TurtleDropCommand.java +++ b/src/main/java/dan200/computercraft/shared/turtle/core/TurtleDropCommand.java @@ -98,7 +98,7 @@ public class TurtleDropCommand implements ITurtleCommand { // Drop the item into the world WorldUtil.dropItemStack( stack, world, oldPosition, direction ); - world.playGlobalEvent( 1000, newPosition, 0 ); // BLOCK_DISPENSER_DISPENSE + world.syncGlobalEvent( 1000, newPosition, 0 ); // BLOCK_DISPENSER_DISPENSE turtle.playAnimation( TurtleAnimation.Wait ); return TurtleCommandResult.success(); } diff --git a/src/main/java/dan200/computercraft/shared/turtle/core/TurtleEquipCommand.java b/src/main/java/dan200/computercraft/shared/turtle/core/TurtleEquipCommand.java index c56350a82..3b37f6082 100644 --- a/src/main/java/dan200/computercraft/shared/turtle/core/TurtleEquipCommand.java +++ b/src/main/java/dan200/computercraft/shared/turtle/core/TurtleEquipCommand.java @@ -38,7 +38,7 @@ public class TurtleEquipCommand implements ITurtleCommand ItemStack newUpgradeStack; Inventory inventory = turtle.getInventory(); ItemStorage storage = ItemStorage.wrap( turtle.getInventory() ); - ItemStack selectedStack = inventory.getInvStack( turtle.getSelectedSlot() ); + ItemStack selectedStack = inventory.getStack( turtle.getSelectedSlot() ); if( !selectedStack.isEmpty() ) { newUpgradeStack = selectedStack.copy(); diff --git a/src/main/java/dan200/computercraft/shared/turtle/core/TurtleInspectCommand.java b/src/main/java/dan200/computercraft/shared/turtle/core/TurtleInspectCommand.java index 64fce7630..d000e8b73 100644 --- a/src/main/java/dan200/computercraft/shared/turtle/core/TurtleInspectCommand.java +++ b/src/main/java/dan200/computercraft/shared/turtle/core/TurtleInspectCommand.java @@ -78,6 +78,6 @@ public class TurtleInspectCommand implements ITurtleCommand private static Object getPropertyValue( Property property, Comparable value ) { if( value instanceof String || value instanceof Number || value instanceof Boolean ) return value; - return property.getName( value ); + return property.name( value ); } } diff --git a/src/main/java/dan200/computercraft/shared/turtle/core/TurtlePlaceCommand.java b/src/main/java/dan200/computercraft/shared/turtle/core/TurtlePlaceCommand.java index 984595429..78ef857f1 100644 --- a/src/main/java/dan200/computercraft/shared/turtle/core/TurtlePlaceCommand.java +++ b/src/main/java/dan200/computercraft/shared/turtle/core/TurtlePlaceCommand.java @@ -50,7 +50,7 @@ public class TurtlePlaceCommand implements ITurtleCommand public TurtleCommandResult execute( @Nonnull ITurtleAccess turtle ) { // Get thing to place - ItemStack stack = turtle.getInventory().getInvStack( turtle.getSelectedSlot() ); + ItemStack stack = turtle.getInventory().getStack( turtle.getSelectedSlot() ); if( stack.isEmpty() ) { return TurtleCommandResult.failure( "No items to place" ); @@ -76,7 +76,7 @@ public class TurtlePlaceCommand implements ITurtleCommand if( remainder != stack ) { // Put the remaining items back - turtle.getInventory().setInvStack( turtle.getSelectedSlot(), remainder ); + turtle.getInventory().setStack( turtle.getSelectedSlot(), remainder ); turtle.getInventory().markDirty(); // Animate and return success @@ -252,7 +252,7 @@ public class TurtlePlaceCommand implements ITurtleCommand // Put everything we collected into the turtles inventory, then return ItemStack remainder = turtlePlayer.unloadInventory( turtle ); - if( !placed && ItemStack.areEqualIgnoreDamage( stack, remainder ) ) + if( !placed && ItemStack.areEqual( stack, remainder ) ) { return stack; } @@ -333,7 +333,7 @@ public class TurtlePlaceCommand implements ITurtleCommand if( !placed && (item instanceof BucketItem || item instanceof BoatItem || item instanceof LilyPadItem || item instanceof GlassBottleItem) ) { TypedActionResult result = stackCopy.use( turtle.getWorld(), turtlePlayer, Hand.MAIN_HAND ); - if( result.getResult() == ActionResult.SUCCESS && !ItemStack.areEqualIgnoreDamage( stack, result.getValue() ) ) + if( result.getResult() == ActionResult.SUCCESS && !ItemStack.areEqual( stack, result.getValue() ) ) { placed = true; turtlePlayer.loadInventory( result.getValue() ); @@ -382,7 +382,7 @@ public class TurtlePlaceCommand implements ITurtleCommand // Put everything we collected into the turtles inventory, then return ItemStack remainder = turtlePlayer.unloadInventory( turtle ); - if( !placed && ItemStack.areEqualIgnoreDamage( stack, remainder ) ) + if( !placed && ItemStack.areEqual( stack, remainder ) ) { return stack; } diff --git a/src/main/java/dan200/computercraft/shared/turtle/core/TurtlePlayer.java b/src/main/java/dan200/computercraft/shared/turtle/core/TurtlePlayer.java index 28f053a21..14026adc9 100644 --- a/src/main/java/dan200/computercraft/shared/turtle/core/TurtlePlayer.java +++ b/src/main/java/dan200/computercraft/shared/turtle/core/TurtlePlayer.java @@ -82,22 +82,22 @@ public final class TurtlePlayer extends FakePlayer { // Load up the fake inventory inventory.selectedSlot = 0; - inventory.setInvStack( 0, currentStack ); + inventory.setStack( 0, currentStack ); } public ItemStack unloadInventory( ITurtleAccess turtle ) { // Get the item we placed with - ItemStack results = inventory.getInvStack( 0 ); - inventory.setInvStack( 0, ItemStack.EMPTY ); + ItemStack results = inventory.getStack( 0 ); + inventory.setStack( 0, ItemStack.EMPTY ); // Store (or drop) anything else we found BlockPos dropPosition = turtle.getPosition(); Direction dropDirection = turtle.getDirection().getOpposite(); ItemStorage storage = ItemStorage.wrap( turtle.getInventory() ); - for( int i = 0; i < inventory.getInvSize(); ++i ) + for( int i = 0; i < inventory.size(); ++i ) { - ItemStack stack = inventory.getInvStack( i ); + ItemStack stack = inventory.getStack( i ); if( !stack.isEmpty() ) { ItemStack remainder = InventoryUtil.storeItems( stack, storage, turtle.getSelectedSlot() ); @@ -105,7 +105,7 @@ public final class TurtlePlayer extends FakePlayer { WorldUtil.dropItemStack( remainder, turtle.getWorld(), dropPosition, dropDirection ); } - inventory.setInvStack( i, ItemStack.EMPTY ); + inventory.setStack( i, ItemStack.EMPTY ); } } inventory.markDirty(); diff --git a/src/main/java/dan200/computercraft/shared/turtle/core/TurtleRefuelCommand.java b/src/main/java/dan200/computercraft/shared/turtle/core/TurtleRefuelCommand.java index 9d79a9e2f..d6a293240 100644 --- a/src/main/java/dan200/computercraft/shared/turtle/core/TurtleRefuelCommand.java +++ b/src/main/java/dan200/computercraft/shared/turtle/core/TurtleRefuelCommand.java @@ -30,7 +30,7 @@ public class TurtleRefuelCommand implements ITurtleCommand public TurtleCommandResult execute( @Nonnull ITurtleAccess turtle ) { int slot = turtle.getSelectedSlot(); - ItemStack stack = turtle.getInventory().getInvStack( slot ); + ItemStack stack = turtle.getInventory().getStack( slot ); if( stack.isEmpty() ) return TurtleCommandResult.failure( "No items to combust" ); TurtleRefuelEvent event = new TurtleRefuelEvent( turtle, stack ); diff --git a/src/main/java/dan200/computercraft/shared/turtle/core/TurtleSuckCommand.java b/src/main/java/dan200/computercraft/shared/turtle/core/TurtleSuckCommand.java index 04d7fa438..1f32d85b3 100644 --- a/src/main/java/dan200/computercraft/shared/turtle/core/TurtleSuckCommand.java +++ b/src/main/java/dan200/computercraft/shared/turtle/core/TurtleSuckCommand.java @@ -157,7 +157,7 @@ public class TurtleSuckCommand implements ITurtleCommand if( storedItems ) { // Play fx - world.playGlobalEvent( 1000, oldPosition, 0 ); // BLOCK_DISPENSER_DISPENSE + world.syncGlobalEvent( 1000, oldPosition, 0 ); // BLOCK_DISPENSER_DISPENSE turtle.playAnimation( TurtleAnimation.Wait ); return TurtleCommandResult.success(); } diff --git a/src/main/java/dan200/computercraft/shared/turtle/inventory/ContainerTurtle.java b/src/main/java/dan200/computercraft/shared/turtle/inventory/ContainerTurtle.java index 21adb6a11..05f69daa8 100644 --- a/src/main/java/dan200/computercraft/shared/turtle/inventory/ContainerTurtle.java +++ b/src/main/java/dan200/computercraft/shared/turtle/inventory/ContainerTurtle.java @@ -11,18 +11,17 @@ import dan200.computercraft.shared.computer.core.IComputer; import dan200.computercraft.shared.computer.core.IContainerComputer; import dan200.computercraft.shared.computer.core.InputState; import dan200.computercraft.shared.util.DefaultPropertyDelegate; -import net.minecraft.container.Container; -import net.minecraft.container.PropertyDelegate; -import net.minecraft.container.Slot; import net.minecraft.entity.player.PlayerEntity; import net.minecraft.entity.player.PlayerInventory; import net.minecraft.inventory.Inventory; import net.minecraft.item.ItemStack; - +import net.minecraft.screen.PropertyDelegate; +import net.minecraft.screen.ScreenHandler; +import net.minecraft.screen.slot.Slot; import javax.annotation.Nonnull; import javax.annotation.Nullable; -public class ContainerTurtle extends Container implements IContainerComputer +public class ContainerTurtle extends ScreenHandler implements IContainerComputer { private static final int PROPERTY_SLOT = 0; @@ -103,13 +102,13 @@ public class ContainerTurtle extends Container implements IContainerComputer @Override public boolean canUse( @Nonnull PlayerEntity player ) { - return inventory.canPlayerUseInv( player ); + return inventory.canPlayerUse( player ); } @Nonnull private ItemStack tryItemMerge( PlayerEntity player, int slotNum, int firstSlot, int lastSlot, boolean reverse ) { - Slot slot = slotList.get( slotNum ); + Slot slot = slots.get( slotNum ); ItemStack originalStack = ItemStack.EMPTY; if( slot != null && slot.hasStack() ) { diff --git a/src/main/java/dan200/computercraft/shared/turtle/items/ItemTurtle.java b/src/main/java/dan200/computercraft/shared/turtle/items/ItemTurtle.java index 00947511b..fd2e25aab 100644 --- a/src/main/java/dan200/computercraft/shared/turtle/items/ItemTurtle.java +++ b/src/main/java/dan200/computercraft/shared/turtle/items/ItemTurtle.java @@ -19,9 +19,8 @@ import net.minecraft.nbt.CompoundTag; import net.minecraft.text.LiteralText; import net.minecraft.text.Text; import net.minecraft.text.TranslatableText; -import net.minecraft.util.DefaultedList; import net.minecraft.util.Identifier; - +import net.minecraft.util.collection.DefaultedList; import javax.annotation.Nonnull; import static dan200.computercraft.shared.turtle.core.TurtleBrain.*; @@ -122,20 +121,20 @@ public class ItemTurtle extends ItemComputerBase implements ITurtleItem if( tag == null ) return null; String key = side == TurtleSide.Left ? NBT_LEFT_UPGRADE : NBT_RIGHT_UPGRADE; - return tag.containsKey( key ) ? TurtleUpgrades.get( tag.getString( key ) ) : null; + return tag.contains( key ) ? TurtleUpgrades.get( tag.getString( key ) ) : null; } @Override public Identifier getOverlay( @Nonnull ItemStack stack ) { CompoundTag tag = stack.getTag(); - return tag != null && tag.containsKey( NBT_OVERLAY ) ? new Identifier( tag.getString( NBT_OVERLAY ) ) : null; + return tag != null && tag.contains( NBT_OVERLAY ) ? new Identifier( tag.getString( NBT_OVERLAY ) ) : null; } @Override public int getFuelLevel( @Nonnull ItemStack stack ) { CompoundTag tag = stack.getTag(); - return tag != null && tag.containsKey( NBT_FUEL ) ? tag.getInt( NBT_FUEL ) : 0; + return tag != null && tag.contains( NBT_FUEL ) ? tag.getInt( NBT_FUEL ) : 0; } } diff --git a/src/main/java/dan200/computercraft/shared/turtle/recipes/TurtleRecipe.java b/src/main/java/dan200/computercraft/shared/turtle/recipes/TurtleRecipe.java index e33e253cc..e4d368e50 100644 --- a/src/main/java/dan200/computercraft/shared/turtle/recipes/TurtleRecipe.java +++ b/src/main/java/dan200/computercraft/shared/turtle/recipes/TurtleRecipe.java @@ -13,9 +13,8 @@ import dan200.computercraft.shared.turtle.items.TurtleItemFactory; import net.minecraft.item.ItemStack; import net.minecraft.recipe.Ingredient; import net.minecraft.recipe.RecipeSerializer; -import net.minecraft.util.DefaultedList; import net.minecraft.util.Identifier; - +import net.minecraft.util.collection.DefaultedList; import javax.annotation.Nonnull; public final class TurtleRecipe extends ComputerFamilyRecipe @@ -41,7 +40,7 @@ public final class TurtleRecipe extends ComputerFamilyRecipe return TurtleItemFactory.create( computerID, label, -1, getFamily(), null, null, 0, null ); } - public static final RecipeSerializer SERIALIZER = new Serializer() + public static final RecipeSerializer SERIALIZER = new dan200.computercraft.shared.computer.recipe.ComputerFamilyRecipe.Serializer() { @Override protected TurtleRecipe create( Identifier identifier, String group, int width, int height, DefaultedList ingredients, ItemStack result, ComputerFamily family ) diff --git a/src/main/java/dan200/computercraft/shared/turtle/recipes/TurtleUpgradeRecipe.java b/src/main/java/dan200/computercraft/shared/turtle/recipes/TurtleUpgradeRecipe.java index a4ed147e2..9b1fba6de 100644 --- a/src/main/java/dan200/computercraft/shared/turtle/recipes/TurtleUpgradeRecipe.java +++ b/src/main/java/dan200/computercraft/shared/turtle/recipes/TurtleUpgradeRecipe.java @@ -65,7 +65,7 @@ public final class TurtleUpgradeRecipe extends SpecialCraftingRecipe boolean finishedRow = false; for( int x = 0; x < inventory.getWidth(); x++ ) { - ItemStack item = inventory.getInvStack( x + y * inventory.getWidth() ); + ItemStack item = inventory.getStack( x + y * inventory.getWidth() ); if( !item.isEmpty() ) { if( finishedRow ) @@ -123,7 +123,7 @@ public final class TurtleUpgradeRecipe extends SpecialCraftingRecipe // Turtle is already found, just check this row is empty for( int x = 0; x < inventory.getWidth(); x++ ) { - ItemStack item = inventory.getInvStack( x + y * inventory.getWidth() ); + ItemStack item = inventory.getStack( x + y * inventory.getWidth() ); if( !item.isEmpty() ) { return ItemStack.EMPTY; diff --git a/src/main/java/dan200/computercraft/shared/turtle/upgrades/TurtleHoe.java b/src/main/java/dan200/computercraft/shared/turtle/upgrades/TurtleHoe.java index 701bd74d1..63165fe46 100644 --- a/src/main/java/dan200/computercraft/shared/turtle/upgrades/TurtleHoe.java +++ b/src/main/java/dan200/computercraft/shared/turtle/upgrades/TurtleHoe.java @@ -43,7 +43,7 @@ public class TurtleHoe extends TurtleTool Material material = state.getMaterial(); return material == Material.PLANT || material == Material.CACTUS || - material == Material.PUMPKIN || + material == Material.GOURD || material == Material.LEAVES || material == Material.UNDERWATER_PLANT; // material == Material.VINE; diff --git a/src/main/java/dan200/computercraft/shared/turtle/upgrades/TurtleInventoryCrafting.java b/src/main/java/dan200/computercraft/shared/turtle/upgrades/TurtleInventoryCrafting.java index 84f12c7b7..925277533 100644 --- a/src/main/java/dan200/computercraft/shared/turtle/upgrades/TurtleInventoryCrafting.java +++ b/src/main/java/dan200/computercraft/shared/turtle/upgrades/TurtleInventoryCrafting.java @@ -15,7 +15,7 @@ import net.minecraft.item.ItemStack; import net.minecraft.recipe.CraftingRecipe; import net.minecraft.recipe.RecipeType; import net.minecraft.server.world.ServerWorld; -import net.minecraft.util.DefaultedList; +import net.minecraft.util.collection.DefaultedList; import net.minecraft.world.World; import javax.annotation.Nonnull; @@ -55,7 +55,7 @@ public class TurtleInventoryCrafting extends CraftingInventory if( x < m_xStart || x >= m_xStart + 3 || y < m_yStart || y >= m_yStart + 3 ) { - if( !m_turtle.getInventory().getInvStack( x + y * TileTurtle.INVENTORY_WIDTH ).isEmpty() ) + if( !m_turtle.getInventory().getStack( x + y * TileTurtle.INVENTORY_WIDTH ).isEmpty() ) { return null; } @@ -96,13 +96,13 @@ public class TurtleInventoryCrafting extends CraftingInventory for( int slot = 0; slot < remainders.size(); slot++ ) { - ItemStack existing = getInvStack( slot ); + ItemStack existing = getStack( slot ); ItemStack remainder = remainders.get( slot ); if( !existing.isEmpty() ) { - takeInvStack( slot, 1 ); - existing = getInvStack( slot ); + removeStack( slot, 1 ); + existing = getStack( slot ); } if( remainder.isEmpty() ) continue; @@ -111,12 +111,12 @@ public class TurtleInventoryCrafting extends CraftingInventory // afterwards). if( existing.isEmpty() ) { - setInvStack( slot, remainder ); + setStack( slot, remainder ); } else if( existing.getItem() == remainder.getItem() && ItemStack.areTagsEqual( existing, remainder ) ) { remainder.increment( existing.getCount() ); - setInvStack( slot, remainder ); + setStack( slot, remainder ); } else { @@ -152,46 +152,46 @@ public class TurtleInventoryCrafting extends CraftingInventory // IInventory implementation @Override - public int getInvSize() + public int size() { return getWidth() * getHeight(); } @Nonnull @Override - public ItemStack getInvStack( int i ) + public ItemStack getStack( int i ) { i = modifyIndex( i ); - return m_turtle.getInventory().getInvStack( i ); + return m_turtle.getInventory().getStack( i ); } @Nonnull @Override - public ItemStack removeInvStack( int i ) + public ItemStack removeStack( int i ) { i = modifyIndex( i ); - return m_turtle.getInventory().removeInvStack( i ); + return m_turtle.getInventory().removeStack( i ); } @Nonnull @Override - public ItemStack takeInvStack( int i, int size ) + public ItemStack removeStack( int i, int size ) { i = modifyIndex( i ); - return m_turtle.getInventory().takeInvStack( i, size ); + return m_turtle.getInventory().removeStack( i, size ); } @Override - public void setInvStack( int i, @Nonnull ItemStack stack ) + public void setStack( int i, @Nonnull ItemStack stack ) { i = modifyIndex( i ); - m_turtle.getInventory().setInvStack( i, stack ); + m_turtle.getInventory().setStack( i, stack ); } @Override - public int getInvMaxStackAmount() + public int getMaxCountPerStack() { - return m_turtle.getInventory().getInvMaxStackAmount(); + return m_turtle.getInventory().getMaxCountPerStack(); } @Override @@ -201,25 +201,25 @@ public class TurtleInventoryCrafting extends CraftingInventory } @Override - public boolean canPlayerUseInv( PlayerEntity player ) + public boolean canPlayerUse( PlayerEntity player ) { return true; } @Override - public boolean isValidInvStack( int i, @Nonnull ItemStack stack ) + public boolean isValid( int i, @Nonnull ItemStack stack ) { i = modifyIndex( i ); - return m_turtle.getInventory().isValidInvStack( i, stack ); + return m_turtle.getInventory().isValid( i, stack ); } @Override public void clear() { - for( int i = 0; i < getInvSize(); i++ ) + for( int i = 0; i < size(); i++ ) { int j = modifyIndex( i ); - m_turtle.getInventory().setInvStack( j, ItemStack.EMPTY ); + m_turtle.getInventory().setStack( j, ItemStack.EMPTY ); } } } diff --git a/src/main/java/dan200/computercraft/shared/turtle/upgrades/TurtleModem.java b/src/main/java/dan200/computercraft/shared/turtle/upgrades/TurtleModem.java index 41e6fd5b8..8301a3cc2 100644 --- a/src/main/java/dan200/computercraft/shared/turtle/upgrades/TurtleModem.java +++ b/src/main/java/dan200/computercraft/shared/turtle/upgrades/TurtleModem.java @@ -138,7 +138,7 @@ public class TurtleModem extends AbstractTurtleUpgrade if( turtle != null ) { CompoundTag turtleNBT = turtle.getUpgradeNBTData( side ); - if( turtleNBT.containsKey( "active" ) ) + if( turtleNBT.contains( "active" ) ) { active = turtleNBT.getBoolean( "active" ); } diff --git a/src/main/java/dan200/computercraft/shared/turtle/upgrades/TurtleShovel.java b/src/main/java/dan200/computercraft/shared/turtle/upgrades/TurtleShovel.java index f7fc0972f..cd6681982 100644 --- a/src/main/java/dan200/computercraft/shared/turtle/upgrades/TurtleShovel.java +++ b/src/main/java/dan200/computercraft/shared/turtle/upgrades/TurtleShovel.java @@ -41,15 +41,15 @@ public class TurtleShovel extends TurtleTool if( !super.canBreakBlock( state, world, pos, player ) ) return false; Material material = state.getMaterial(); - return material == Material.EARTH || - material == Material.SAND || - material == Material.SNOW || - material == Material.CLAY || + return material == Material.SOIL || + material == Material.AGGREGATE || + material == Material.SNOW_LAYER || + material == Material.ORGANIC_PRODUCT || material == Material.SNOW_BLOCK || material == Material.REPLACEABLE_PLANT || material == Material.PLANT || material == Material.CACTUS || - material == Material.PUMPKIN || + material == Material.GOURD || material == Material.LEAVES; // material == Material.VINE; } diff --git a/src/main/java/dan200/computercraft/shared/turtle/upgrades/TurtleTool.java b/src/main/java/dan200/computercraft/shared/turtle/upgrades/TurtleTool.java index 386ca58de..0667f6909 100644 --- a/src/main/java/dan200/computercraft/shared/turtle/upgrades/TurtleTool.java +++ b/src/main/java/dan200/computercraft/shared/turtle/upgrades/TurtleTool.java @@ -241,11 +241,11 @@ public class TurtleTool extends AbstractTurtleUpgrade // to consult there before making any changes. // Play the destruction sound and particles - world.playGlobalEvent( 2001, blockPosition, Block.getRawIdFromState( state ) ); + world.syncGlobalEvent( 2001, blockPosition, Block.getRawIdFromState( state ) ); // Destroy the block state.getBlock().onBreak( world, blockPosition, state, turtlePlayer ); - if( world.clearBlockState( blockPosition, false ) ) + if( world.removeBlock( blockPosition, false ) ) { state.getBlock().onBroken( world, blockPosition, state ); if( turtlePlayer.isUsingEffectiveTool( state ) ) diff --git a/src/main/java/dan200/computercraft/shared/util/DefaultPropertyDelegate.java b/src/main/java/dan200/computercraft/shared/util/DefaultPropertyDelegate.java index 543392a46..736b50f6d 100644 --- a/src/main/java/dan200/computercraft/shared/util/DefaultPropertyDelegate.java +++ b/src/main/java/dan200/computercraft/shared/util/DefaultPropertyDelegate.java @@ -6,7 +6,7 @@ package dan200.computercraft.shared.util; -import net.minecraft.container.PropertyDelegate; +import net.minecraft.screen.PropertyDelegate; public interface DefaultPropertyDelegate extends PropertyDelegate { diff --git a/src/main/java/dan200/computercraft/shared/util/DefaultSidedInventory.java b/src/main/java/dan200/computercraft/shared/util/DefaultSidedInventory.java index b7f22f974..bed6dc341 100644 --- a/src/main/java/dan200/computercraft/shared/util/DefaultSidedInventory.java +++ b/src/main/java/dan200/computercraft/shared/util/DefaultSidedInventory.java @@ -16,13 +16,13 @@ import javax.annotation.Nullable; public interface DefaultSidedInventory extends DefaultInventory, SidedInventory { @Override - default boolean canInsertInvStack( int slot, @Nonnull ItemStack stack, @Nullable Direction side ) + default boolean canInsert( int slot, @Nonnull ItemStack stack, @Nullable Direction side ) { - return isValidInvStack( slot, stack ); + return isValid( slot, stack ); } @Override - default boolean canExtractInvStack( int slot, @Nonnull ItemStack stack, @Nonnull Direction side ) + default boolean canExtract( int slot, @Nonnull ItemStack stack, @Nonnull Direction side ) { return true; } diff --git a/src/main/java/dan200/computercraft/shared/util/ImpostorRecipe.java b/src/main/java/dan200/computercraft/shared/util/ImpostorRecipe.java index 7740f6eb4..8c419fbaa 100644 --- a/src/main/java/dan200/computercraft/shared/util/ImpostorRecipe.java +++ b/src/main/java/dan200/computercraft/shared/util/ImpostorRecipe.java @@ -10,13 +10,13 @@ import com.google.gson.JsonObject; import dan200.computercraft.ComputerCraft; import net.minecraft.inventory.CraftingInventory; import net.minecraft.item.ItemStack; +import net.minecraft.network.PacketByteBuf; import net.minecraft.recipe.Ingredient; import net.minecraft.recipe.RecipeSerializer; import net.minecraft.recipe.ShapedRecipe; -import net.minecraft.util.DefaultedList; import net.minecraft.util.Identifier; import net.minecraft.util.JsonHelper; -import net.minecraft.util.PacketByteBuf; +import net.minecraft.util.collection.DefaultedList; import net.minecraft.world.World; import javax.annotation.Nonnull; diff --git a/src/main/java/dan200/computercraft/shared/util/ImpostorShapelessRecipe.java b/src/main/java/dan200/computercraft/shared/util/ImpostorShapelessRecipe.java index e159f430d..3ce3233d3 100644 --- a/src/main/java/dan200/computercraft/shared/util/ImpostorShapelessRecipe.java +++ b/src/main/java/dan200/computercraft/shared/util/ImpostorShapelessRecipe.java @@ -11,14 +11,14 @@ import com.google.gson.JsonObject; import com.google.gson.JsonParseException; import net.minecraft.inventory.CraftingInventory; import net.minecraft.item.ItemStack; +import net.minecraft.network.PacketByteBuf; import net.minecraft.recipe.Ingredient; import net.minecraft.recipe.RecipeSerializer; import net.minecraft.recipe.ShapedRecipe; import net.minecraft.recipe.ShapelessRecipe; -import net.minecraft.util.DefaultedList; import net.minecraft.util.Identifier; import net.minecraft.util.JsonHelper; -import net.minecraft.util.PacketByteBuf; +import net.minecraft.util.collection.DefaultedList; import net.minecraft.world.World; import javax.annotation.Nonnull; diff --git a/src/main/java/dan200/computercraft/shared/util/InventoryUtil.java b/src/main/java/dan200/computercraft/shared/util/InventoryUtil.java index 52a650c77..e8c4c84f1 100644 --- a/src/main/java/dan200/computercraft/shared/util/InventoryUtil.java +++ b/src/main/java/dan200/computercraft/shared/util/InventoryUtil.java @@ -26,7 +26,7 @@ public final class InventoryUtil public static boolean areItemsEqual( @Nonnull ItemStack a, @Nonnull ItemStack b ) { - return a == b || ItemStack.areEqualIgnoreDamage( a, b ); + return a == b || ItemStack.areEqual( a, b ); } public static boolean areItemsStackable( @Nonnull ItemStack a, @Nonnull ItemStack b ) diff --git a/src/main/java/dan200/computercraft/shared/util/ItemStorage.java b/src/main/java/dan200/computercraft/shared/util/ItemStorage.java index c9d951dfe..c218ae9bc 100644 --- a/src/main/java/dan200/computercraft/shared/util/ItemStorage.java +++ b/src/main/java/dan200/computercraft/shared/util/ItemStorage.java @@ -42,7 +42,7 @@ public interface ItemStorage private void setAndDirty( int slot, @Nonnull ItemStack stack ) { - inventory.setInvStack( slot, stack ); + inventory.setStack( slot, stack ); inventory.markDirty(); } @@ -54,14 +54,14 @@ public interface ItemStorage @Override public int size() { - return inventory.getInvSize(); + return inventory.size(); } @Override @Nonnull public ItemStack take( int slot, int limit, @Nonnull ItemStack filter, boolean simulate ) { - ItemStack existing = inventory.getInvStack( slot ); + ItemStack existing = inventory.getStack( slot ); if( existing.isEmpty() || !canExtract( slot, existing ) || (!filter.isEmpty() && !areStackable( existing, filter )) ) { @@ -91,12 +91,12 @@ public interface ItemStorage @Nonnull public ItemStack store( int slot, @Nonnull ItemStack stack, boolean simulate ) { - if( stack.isEmpty() || !inventory.isValidInvStack( slot, stack ) ) return stack; + if( stack.isEmpty() || !inventory.isValid( slot, stack ) ) return stack; - ItemStack existing = inventory.getInvStack( slot ); + ItemStack existing = inventory.getStack( slot ); if( existing.isEmpty() ) { - int limit = Math.min( stack.getMaxCount(), inventory.getInvMaxStackAmount() ); + int limit = Math.min( stack.getMaxCount(), inventory.getMaxCountPerStack() ); if( limit <= 0 ) return stack; if( stack.getCount() < limit ) @@ -114,7 +114,7 @@ public interface ItemStorage } else if( areStackable( stack, existing ) ) { - int limit = Math.min( existing.getMaxCount(), inventory.getInvMaxStackAmount() ) - existing.getCount(); + int limit = Math.min( existing.getMaxCount(), inventory.getMaxCountPerStack() ) - existing.getCount(); if( limit <= 0 ) return stack; if( stack.getCount() < limit ) @@ -160,20 +160,20 @@ public interface ItemStorage @Override public int size() { - return inventory.getInvAvailableSlots( facing ).length; + return inventory.getAvailableSlots( facing ).length; } @Override protected boolean canExtract( int slot, ItemStack stack ) { - return super.canExtract( slot, stack ) && inventory.canExtractInvStack( slot, stack, facing ); + return super.canExtract( slot, stack ) && inventory.canExtract( slot, stack, facing ); } @Nonnull @Override public ItemStack take( int slot, int limit, @Nonnull ItemStack filter, boolean simulate ) { - int[] slots = inventory.getInvAvailableSlots( facing ); + int[] slots = inventory.getAvailableSlots( facing ); return slot >= 0 && slot < slots.length ? super.take( slots[slot], limit, filter, simulate ) : ItemStack.EMPTY; } @@ -181,11 +181,11 @@ public interface ItemStorage @Override public ItemStack store( int slot, @Nonnull ItemStack stack, boolean simulate ) { - int[] slots = inventory.getInvAvailableSlots( facing ); + int[] slots = inventory.getAvailableSlots( facing ); if( slot < 0 || slot >= slots.length ) return stack; int mappedSlot = slots[slot]; - if( !inventory.canInsertInvStack( slot, stack, facing ) ) return stack; + if( !inventory.canInsert( slot, stack, facing ) ) return stack; return super.store( mappedSlot, stack, simulate ); } } diff --git a/src/main/java/dan200/computercraft/shared/util/NBTUtil.java b/src/main/java/dan200/computercraft/shared/util/NBTUtil.java index 556c34dcd..b0ae60328 100644 --- a/src/main/java/dan200/computercraft/shared/util/NBTUtil.java +++ b/src/main/java/dan200/computercraft/shared/util/NBTUtil.java @@ -92,8 +92,8 @@ public final class NBTUtil Map map = new HashMap<>( len ); for( int i = 0; i < len; i++ ) { - Object key = fromNBTTag( c.getTag( "k" + i ) ); - Object value = fromNBTTag( c.getTag( "v" + i ) ); + Object key = fromNBTTag( c.get( "k" + i ) ); + Object value = fromNBTTag( c.get( "v" + i ) ); if( key != null && value != null ) map.put( key, value ); } return map; @@ -124,7 +124,7 @@ public final class NBTUtil Map map = new HashMap<>( compound.getSize() ); for( String key : compound.getKeys() ) { - Object value = toLua( compound.getTag( key ) ); + Object value = toLua( compound.get( key ) ); if( value != null ) map.put( key, value ); } return map; @@ -165,9 +165,9 @@ public final class NBTUtil for( int i = 0; i < len; i++ ) { String key = Integer.toString( i ); - if( tag.containsKey( key ) ) + if( tag.contains( key ) ) { - objects[i] = fromNBTTag( tag.getTag( key ) ); + objects[i] = fromNBTTag( tag.get( key ) ); } } return objects; diff --git a/src/main/java/dan200/computercraft/shared/util/NamedBlockEntityType.java b/src/main/java/dan200/computercraft/shared/util/NamedBlockEntityType.java index 2add3de37..8d2df538a 100644 --- a/src/main/java/dan200/computercraft/shared/util/NamedBlockEntityType.java +++ b/src/main/java/dan200/computercraft/shared/util/NamedBlockEntityType.java @@ -13,8 +13,8 @@ import net.minecraft.SharedConstants; import net.minecraft.block.Block; import net.minecraft.block.entity.BlockEntity; import net.minecraft.block.entity.BlockEntityType; -import net.minecraft.datafixers.Schemas; -import net.minecraft.datafixers.TypeReferences; +import net.minecraft.datafixer.Schemas; +import net.minecraft.datafixer.TypeReferences; import net.minecraft.util.Identifier; import net.minecraft.util.registry.MutableRegistry; diff --git a/src/main/java/dan200/computercraft/shared/util/Palette.java b/src/main/java/dan200/computercraft/shared/util/Palette.java index 856df6aa6..7df7215d3 100644 --- a/src/main/java/dan200/computercraft/shared/util/Palette.java +++ b/src/main/java/dan200/computercraft/shared/util/Palette.java @@ -95,7 +95,7 @@ public class Palette public void readFromNBT( CompoundTag nbt ) { - if( !nbt.containsKey( "term_palette" ) ) return; + if( !nbt.contains( "term_palette" ) ) return; int[] rgb8 = nbt.getIntArray( "term_palette" ); if( rgb8.length != colours.length ) return; diff --git a/src/main/java/dan200/computercraft/shared/util/RecipeUtil.java b/src/main/java/dan200/computercraft/shared/util/RecipeUtil.java index b367b34d4..c96405cc1 100644 --- a/src/main/java/dan200/computercraft/shared/util/RecipeUtil.java +++ b/src/main/java/dan200/computercraft/shared/util/RecipeUtil.java @@ -11,9 +11,8 @@ import com.google.common.collect.Sets; import com.google.gson.*; import dan200.computercraft.shared.computer.core.ComputerFamily; import net.minecraft.recipe.Ingredient; -import net.minecraft.util.DefaultedList; import net.minecraft.util.JsonHelper; - +import net.minecraft.util.collection.DefaultedList; import java.util.Map; import java.util.Set; diff --git a/src/main/java/dan200/computercraft/shared/util/RecordUtil.java b/src/main/java/dan200/computercraft/shared/util/RecordUtil.java index 7853fd70b..e698c6c6c 100644 --- a/src/main/java/dan200/computercraft/shared/util/RecordUtil.java +++ b/src/main/java/dan200/computercraft/shared/util/RecordUtil.java @@ -27,7 +27,7 @@ public final class RecordUtil public static void playRecord( SoundEvent record, String recordInfo, World world, BlockPos pos ) { NetworkMessage packet = record != null ? new PlayRecordClientMessage( pos, record, recordInfo ) : new PlayRecordClientMessage( pos ); - NetworkHandler.sendToAllAround( packet, world, new Vec3d( pos ), 64 ); + NetworkHandler.sendToAllAround( packet, world, new Vec3d(pos.getX(), pos.getY(), pos.getZ()), 64 ); } public static String getRecordInfo( @Nonnull ItemStack recordStack ) diff --git a/src/main/java/dan200/computercraft/shared/util/TickScheduler.java b/src/main/java/dan200/computercraft/shared/util/TickScheduler.java index 4db62d631..a5253bf55 100644 --- a/src/main/java/dan200/computercraft/shared/util/TickScheduler.java +++ b/src/main/java/dan200/computercraft/shared/util/TickScheduler.java @@ -50,7 +50,7 @@ public final class TickScheduler World world = tile.getWorld(); BlockPos pos = tile.getPos(); - if( world != null && pos != null && world.isBlockLoaded( pos ) && world.getBlockEntity( pos ) == tile ) + if( world != null && pos != null && world.isChunkLoaded(pos) && world.getBlockEntity( pos ) == tile ) { world.getBlockTickScheduler().schedule( pos, tile.getCachedState().getBlock(), 0 ); } diff --git a/src/main/java/dan200/computercraft/shared/util/ValidatingSlot.java b/src/main/java/dan200/computercraft/shared/util/ValidatingSlot.java index b2cc46918..12ed97dba 100644 --- a/src/main/java/dan200/computercraft/shared/util/ValidatingSlot.java +++ b/src/main/java/dan200/computercraft/shared/util/ValidatingSlot.java @@ -6,9 +6,9 @@ package dan200.computercraft.shared.util; -import net.minecraft.container.Slot; import net.minecraft.inventory.Inventory; import net.minecraft.item.ItemStack; +import net.minecraft.screen.slot.Slot; public class ValidatingSlot extends Slot { @@ -23,6 +23,6 @@ public class ValidatingSlot extends Slot @Override public boolean canInsert( ItemStack stack ) { - return inventory.isValidInvStack( invSlot, stack ); + return inventory.isValid( invSlot, stack ); } } diff --git a/src/main/java/dan200/computercraft/shared/util/WaterloggableBlock.java b/src/main/java/dan200/computercraft/shared/util/WaterloggableBlock.java index 0a82d3d93..13907526f 100644 --- a/src/main/java/dan200/computercraft/shared/util/WaterloggableBlock.java +++ b/src/main/java/dan200/computercraft/shared/util/WaterloggableBlock.java @@ -15,7 +15,7 @@ import net.minecraft.state.property.BooleanProperty; import net.minecraft.state.property.Properties; import net.minecraft.util.math.BlockPos; import net.minecraft.util.math.Direction; -import net.minecraft.world.IWorld; +import net.minecraft.world.WorldAccess; /** * Represents a block which can be filled with water @@ -44,7 +44,7 @@ public interface WaterloggableBlock extends Waterloggable * @param world The position of this block * @param pos The world this block exists in */ - default void updateWaterloggedPostPlacement( BlockState state, IWorld world, BlockPos pos ) + default void updateWaterloggedPostPlacement( BlockState state, WorldAccess world, BlockPos pos ) { if( state.get( WATERLOGGED ) ) { diff --git a/src/main/java/dan200/computercraft/shared/util/WorldUtil.java b/src/main/java/dan200/computercraft/shared/util/WorldUtil.java index c440de12c..d08db61df 100644 --- a/src/main/java/dan200/computercraft/shared/util/WorldUtil.java +++ b/src/main/java/dan200/computercraft/shared/util/WorldUtil.java @@ -64,7 +64,7 @@ public final class WorldUtil Vec3d vecEnd = vecStart.add( vecDir.x * distance, vecDir.y * distance, vecDir.z * distance ); // Raycast for blocks - ENTITY.setPosition( vecStart.x, vecStart.y, vecStart.z ); + ENTITY.updatePosition( vecStart.x, vecStart.y, vecStart.z ); HitResult result = world.rayTrace( new RayTraceContext( vecStart, vecEnd, RayTraceContext.ShapeType.OUTLINE, RayTraceContext.FluidHandling.NONE, ENTITY ) ); if( result != null && result.getType() == HitResult.Type.BLOCK ) {