1
0
mirror of https://github.com/SquidDev-CC/CC-Tweaked synced 2024-12-14 20:20:30 +00:00

Merge branch 'mc-1.15.x' into mc-1.16.x

This commit is contained in:
Jonathan Coates 2021-06-05 11:36:08 +01:00
commit f38a6a9d43
No known key found for this signature in database
GPG Key ID: B9E431FF07C98D06
56 changed files with 2002 additions and 1032 deletions

View File

@ -15,7 +15,6 @@ buildscript {
dependencies { dependencies {
classpath 'com.google.code.gson:gson:2.8.1' classpath 'com.google.code.gson:gson:2.8.1'
classpath 'net.minecraftforge.gradle:ForgeGradle:4.1.9' classpath 'net.minecraftforge.gradle:ForgeGradle:4.1.9'
classpath 'net.sf.proguard:proguard-gradle:6.1.0beta2'
classpath 'org.spongepowered:mixingradle:0.7-SNAPSHOT' classpath 'org.spongepowered:mixingradle:0.7-SNAPSHOT'
} }
} }
@ -199,59 +198,6 @@ jar {
} }
} }
import java.nio.charset.StandardCharsets
import java.nio.file.*
import java.util.zip.*
import com.google.gson.GsonBuilder
import com.google.gson.JsonElement
import com.hierynomus.gradle.license.tasks.LicenseCheck
import com.hierynomus.gradle.license.tasks.LicenseFormat
import proguard.gradle.ProGuardTask
task proguard(type: ProGuardTask, dependsOn: jar) {
description "Removes unused shadowed classes from the jar"
group "compact"
injars jar.archivePath
outjars "${jar.archivePath.absolutePath.replace(".jar", "")}-min.jar"
// Add the main runtime jar and all non-shadowed dependencies
libraryjars "${System.getProperty('java.home')}/lib/rt.jar"
libraryjars "${System.getProperty('java.home')}/lib/jce.jar"
doFirst {
sourceSets.main.compileClasspath
.filter { !it.name.contains("Cobalt") }
.each { libraryjars it }
}
// We want to avoid as much obfuscation as possible. We're only doing this to shrink code size.
dontobfuscate; dontoptimize; keepattributes; keepparameternames
// Proguard will remove directories by default, but that breaks JarMount.
keepdirectories 'data/computercraft/lua**'
// Preserve ComputerCraft classes - we only want to strip shadowed files.
keep 'class dan200.computercraft.** { *; }'
// LWJGL and Apache bundle Java 9 versions, which is great, but rather breaks Proguard
dontwarn 'module-info'
dontwarn 'org.apache.**,org.lwjgl.**'
}
task proguardMove(dependsOn: proguard) {
description "Replace the original jar with the minified version"
group "compact"
doLast {
Files.move(
file("${jar.archivePath.absolutePath.replace(".jar", "")}-min.jar").toPath(),
file(jar.archivePath).toPath(),
StandardCopyOption.REPLACE_EXISTING
)
}
}
processResources { processResources {
inputs.property "version", mod_version inputs.property "version", mod_version
inputs.property "mcversion", mc_version inputs.property "mcversion", mc_version
@ -285,49 +231,6 @@ processResources {
} }
} }
task compressJson(dependsOn: jar) {
group "compact"
description "Minifies all JSON files, stripping whitespace"
def jarPath = file(jar.archivePath)
def tempPath = File.createTempFile("input", ".jar", temporaryDir)
tempPath.deleteOnExit()
def gson = new GsonBuilder().create()
doLast {
// Copy over all files in the current jar to the new one, running json files from GSON. As pretty printing
// is turned off, they should be minified.
new ZipFile(jarPath).withCloseable { inJar ->
tempPath.getParentFile().mkdirs()
new ZipOutputStream(new BufferedOutputStream(new FileOutputStream(tempPath))).withCloseable { outJar ->
inJar.entries().each { entry ->
if(entry.directory) {
outJar.putNextEntry(entry)
} else if(!entry.name.endsWith(".json")) {
outJar.putNextEntry(entry)
inJar.getInputStream(entry).withCloseable { outJar << it }
} else {
ZipEntry newEntry = new ZipEntry(entry.name)
newEntry.setTime(entry.time)
outJar.putNextEntry(newEntry)
def element = inJar.getInputStream(entry).withCloseable { gson.fromJson(it.newReader("UTF8"), JsonElement.class) }
outJar.write(gson.toJson(element).getBytes(StandardCharsets.UTF_8))
}
}
}
}
// And replace the original jar again
Files.move(tempPath.toPath(), jarPath.toPath(), StandardCopyOption.REPLACE_EXISTING)
}
}
assemble.dependsOn compressJson
// Web tasks // Web tasks
import org.apache.tools.ant.taskdefs.condition.Os import org.apache.tools.ant.taskdefs.condition.Os
@ -399,6 +302,10 @@ jacocoTestReport {
check.dependsOn jacocoTestReport check.dependsOn jacocoTestReport
import com.hierynomus.gradle.license.tasks.LicenseCheck
import com.hierynomus.gradle.license.tasks.LicenseFormat
license { license {
mapping("java", "SLASHSTAR_STYLE") mapping("java", "SLASHSTAR_STYLE")
strictCheck true strictCheck true
@ -452,7 +359,7 @@ task setupServer(type: Copy) {
tasks.register('testInGame', JavaExec.class).configure { tasks.register('testInGame', JavaExec.class).configure {
it.group('test server') it.group('test server')
it.description("Runs tests on a temporary Minecraft server.") it.description("Runs tests on a temporary Minecraft server.")
it.dependsOn(setupServer, 'prepareRunTestServer') it.dependsOn(setupServer, 'prepareRunTestServer', 'cleanTestInGame')
// Copy from runTestServer. We do it in this slightly odd way as runTestServer // Copy from runTestServer. We do it in this slightly odd way as runTestServer
// isn't created until the task is configured (which is no good for us). // isn't created until the task is configured (which is no good for us).

View File

@ -0,0 +1,39 @@
/*
* This file is part of ComputerCraft - http://www.computercraft.info
* Copyright Daniel Ratcliffe, 2011-2021. Do not distribute without permission.
* Send enquiries to dratcliffe@gmail.com
*/
package dan200.computercraft.client;
import dan200.computercraft.ComputerCraft;
import dan200.computercraft.shared.peripheral.monitor.ClientMonitor;
import net.minecraftforge.api.distmarker.Dist;
import net.minecraftforge.client.event.ClientPlayerNetworkEvent;
import net.minecraftforge.event.world.WorldEvent;
import net.minecraftforge.eventbus.api.SubscribeEvent;
import net.minecraftforge.fml.common.Mod;
@Mod.EventBusSubscriber( modid = ComputerCraft.MOD_ID, value = Dist.CLIENT )
public class ClientHooks
{
@SubscribeEvent
public static void onWorldUnload( WorldEvent.Unload event )
{
if( event.getWorld().isClientSide() )
{
ClientMonitor.destroyAll();
}
}
@SubscribeEvent
public static void onLogIn( ClientPlayerNetworkEvent.LoggedInEvent event )
{
ComputerCraft.clientComputerRegistry.reset();
}
@SubscribeEvent
public static void onLogOut( ClientPlayerNetworkEvent.LoggedOutEvent event )
{
ComputerCraft.clientComputerRegistry.reset();
}
}

View File

@ -6,31 +6,39 @@
package dan200.computercraft.client; package dan200.computercraft.client;
import dan200.computercraft.ComputerCraft; import dan200.computercraft.ComputerCraft;
import dan200.computercraft.client.gui.*;
import dan200.computercraft.client.render.TileEntityMonitorRenderer;
import dan200.computercraft.client.render.TileEntityTurtleRenderer;
import dan200.computercraft.client.render.TurtleModelLoader; import dan200.computercraft.client.render.TurtleModelLoader;
import dan200.computercraft.client.render.TurtlePlayerRenderer;
import dan200.computercraft.shared.Registry; import dan200.computercraft.shared.Registry;
import dan200.computercraft.shared.common.IColouredItem; import dan200.computercraft.shared.common.IColouredItem;
import dan200.computercraft.shared.computer.inventory.ContainerComputer;
import dan200.computercraft.shared.computer.inventory.ContainerViewComputer;
import dan200.computercraft.shared.media.items.ItemDisk; import dan200.computercraft.shared.media.items.ItemDisk;
import dan200.computercraft.shared.media.items.ItemTreasureDisk; import dan200.computercraft.shared.media.items.ItemTreasureDisk;
import dan200.computercraft.shared.pocket.inventory.ContainerPocketComputer;
import dan200.computercraft.shared.pocket.items.ItemPocketComputer; import dan200.computercraft.shared.pocket.items.ItemPocketComputer;
import dan200.computercraft.shared.util.Colour; import dan200.computercraft.shared.util.Colour;
import net.minecraft.client.renderer.model.IBakedModel; import net.minecraft.client.gui.ScreenManager;
import net.minecraft.client.renderer.model.IUnbakedModel; import net.minecraft.client.renderer.RenderType;
import net.minecraft.client.renderer.RenderTypeLookup;
import net.minecraft.client.renderer.model.ModelResourceLocation; import net.minecraft.client.renderer.model.ModelResourceLocation;
import net.minecraft.inventory.container.PlayerContainer; import net.minecraft.item.IItemPropertyGetter;
import net.minecraft.item.Item;
import net.minecraft.item.ItemModelsProperties;
import net.minecraft.util.ResourceLocation; import net.minecraft.util.ResourceLocation;
import net.minecraftforge.api.distmarker.Dist; import net.minecraftforge.api.distmarker.Dist;
import net.minecraftforge.client.event.ColorHandlerEvent; import net.minecraftforge.client.event.ColorHandlerEvent;
import net.minecraftforge.client.event.ModelBakeEvent;
import net.minecraftforge.client.event.ModelRegistryEvent; import net.minecraftforge.client.event.ModelRegistryEvent;
import net.minecraftforge.client.event.TextureStitchEvent;
import net.minecraftforge.client.model.ModelLoader; import net.minecraftforge.client.model.ModelLoader;
import net.minecraftforge.client.model.ModelLoaderRegistry; import net.minecraftforge.client.model.ModelLoaderRegistry;
import net.minecraftforge.client.model.SimpleModelTransform;
import net.minecraftforge.eventbus.api.SubscribeEvent; import net.minecraftforge.eventbus.api.SubscribeEvent;
import net.minecraftforge.fml.client.registry.RenderingRegistry;
import net.minecraftforge.fml.common.Mod; import net.minecraftforge.fml.common.Mod;
import net.minecraftforge.fml.event.lifecycle.FMLClientSetupEvent;
import java.util.HashSet; import java.util.function.Supplier;
import java.util.Map;
/** /**
* Registers textures and models for items. * Registers textures and models for items.
@ -39,6 +47,7 @@ import java.util.Map;
public final class ClientRegistry public final class ClientRegistry
{ {
private static final String[] EXTRA_MODELS = new String[] { private static final String[] EXTRA_MODELS = new String[] {
// Turtle upgrades
"turtle_modem_normal_off_left", "turtle_modem_normal_off_left",
"turtle_modem_normal_on_left", "turtle_modem_normal_on_left",
"turtle_modem_normal_off_right", "turtle_modem_normal_off_right",
@ -54,56 +63,20 @@ public final class ClientRegistry
"turtle_speaker_upgrade_left", "turtle_speaker_upgrade_left",
"turtle_speaker_upgrade_right", "turtle_speaker_upgrade_right",
// Turtle block renderer
"turtle_colour", "turtle_colour",
"turtle_elf_overlay", "turtle_elf_overlay",
}; };
private static final String[] EXTRA_TEXTURES = new String[] {
// TODO: Gather these automatically from the model. Sadly the model loader isn't available
// when stitching textures.
"block/turtle_colour",
"block/turtle_elf_overlay",
"block/turtle_crafty_face",
"block/turtle_speaker_face",
};
private ClientRegistry() {} private ClientRegistry() {}
@SubscribeEvent @SubscribeEvent
public static void registerModels( ModelRegistryEvent event ) public static void registerModels( ModelRegistryEvent event )
{ {
ModelLoaderRegistry.registerLoader( new ResourceLocation( ComputerCraft.MOD_ID, "turtle" ), TurtleModelLoader.INSTANCE ); ModelLoaderRegistry.registerLoader( new ResourceLocation( ComputerCraft.MOD_ID, "turtle" ), TurtleModelLoader.INSTANCE );
} for( String model : EXTRA_MODELS )
@SubscribeEvent
public static void onTextureStitchEvent( TextureStitchEvent.Pre event )
{ {
if( !event.getMap().location().equals( PlayerContainer.BLOCK_ATLAS ) ) return; ModelLoader.addSpecialModel( new ModelResourceLocation( new ResourceLocation( ComputerCraft.MOD_ID, model ), "inventory" ) );
for( String extra : EXTRA_TEXTURES )
{
event.addSprite( new ResourceLocation( ComputerCraft.MOD_ID, extra ) );
}
}
@SubscribeEvent
public static void onModelBakeEvent( ModelBakeEvent event )
{
// Load all extra models
ModelLoader loader = event.getModelLoader();
Map<ResourceLocation, IBakedModel> registry = event.getModelRegistry();
for( String modelName : EXTRA_MODELS )
{
ResourceLocation location = new ResourceLocation( ComputerCraft.MOD_ID, "item/" + modelName );
IUnbakedModel model = loader.getModel( location );
model.getMaterials( loader::getModel, new HashSet<>() );
IBakedModel baked = model.bake( loader, ModelLoader.defaultTextureGetter(), SimpleModelTransform.IDENTITY, location );
if( baked != null )
{
registry.put( new ModelResourceLocation( new ResourceLocation( ComputerCraft.MOD_ID, modelName ), "inventory" ), baked );
}
} }
} }
@ -148,4 +121,61 @@ public final class ClientRegistry
Registry.ModBlocks.TURTLE_NORMAL.get(), Registry.ModBlocks.TURTLE_ADVANCED.get() Registry.ModBlocks.TURTLE_NORMAL.get(), Registry.ModBlocks.TURTLE_ADVANCED.get()
); );
} }
@SubscribeEvent
public static void setupClient( FMLClientSetupEvent event )
{
registerContainers();
// While turtles themselves are not transparent, their upgrades may be.
RenderTypeLookup.setRenderLayer( Registry.ModBlocks.TURTLE_NORMAL.get(), RenderType.translucent() );
RenderTypeLookup.setRenderLayer( Registry.ModBlocks.TURTLE_ADVANCED.get(), RenderType.translucent() );
// Monitors' textures have transparent fronts and so count as cutouts.
RenderTypeLookup.setRenderLayer( Registry.ModBlocks.MONITOR_NORMAL.get(), RenderType.cutout() );
RenderTypeLookup.setRenderLayer( Registry.ModBlocks.MONITOR_ADVANCED.get(), RenderType.cutout() );
// Setup TESRs
net.minecraftforge.fml.client.registry.ClientRegistry.bindTileEntityRenderer( Registry.ModTiles.MONITOR_NORMAL.get(), TileEntityMonitorRenderer::new );
net.minecraftforge.fml.client.registry.ClientRegistry.bindTileEntityRenderer( Registry.ModTiles.MONITOR_ADVANCED.get(), TileEntityMonitorRenderer::new );
net.minecraftforge.fml.client.registry.ClientRegistry.bindTileEntityRenderer( Registry.ModTiles.TURTLE_NORMAL.get(), TileEntityTurtleRenderer::new );
net.minecraftforge.fml.client.registry.ClientRegistry.bindTileEntityRenderer( Registry.ModTiles.TURTLE_ADVANCED.get(), TileEntityTurtleRenderer::new );
RenderingRegistry.registerEntityRenderingHandler( Registry.ModEntities.TURTLE_PLAYER.get(), TurtlePlayerRenderer::new );
registerItemProperty( "state",
( stack, world, player ) -> ItemPocketComputer.getState( stack ).ordinal(),
Registry.ModItems.POCKET_COMPUTER_NORMAL, Registry.ModItems.POCKET_COMPUTER_ADVANCED
);
registerItemProperty( "state",
( stack, world, player ) -> IColouredItem.getColourBasic( stack ) != -1 ? 1 : 0,
Registry.ModItems.POCKET_COMPUTER_NORMAL, Registry.ModItems.POCKET_COMPUTER_ADVANCED
);
}
@SafeVarargs
private static void registerItemProperty( String name, IItemPropertyGetter getter, Supplier<? extends Item>... items )
{
ResourceLocation id = new ResourceLocation( ComputerCraft.MOD_ID, name );
for( Supplier<? extends Item> item : items )
{
ItemModelsProperties.register( item.get(), id, getter );
}
}
private static void registerContainers()
{
// My IDE doesn't think so, but we do actually need these generics.
ScreenManager.<ContainerComputer, GuiComputer<ContainerComputer>>register( Registry.ModContainers.COMPUTER.get(), GuiComputer::create );
ScreenManager.<ContainerPocketComputer, GuiComputer<ContainerPocketComputer>>register( Registry.ModContainers.POCKET_COMPUTER.get(), GuiComputer::createPocket );
ScreenManager.register( Registry.ModContainers.TURTLE.get(), GuiTurtle::new );
ScreenManager.register( Registry.ModContainers.PRINTER.get(), GuiPrinter::new );
ScreenManager.register( Registry.ModContainers.DISK_DRIVE.get(), GuiDiskDrive::new );
ScreenManager.register( Registry.ModContainers.PRINTOUT.get(), GuiPrintout::new );
ScreenManager.<ContainerViewComputer, GuiComputer<ContainerViewComputer>>register( Registry.ModContainers.VIEW_COMPUTER.get(), GuiComputer::createView );
}
} }

View File

@ -1,108 +0,0 @@
/*
* This file is part of ComputerCraft - http://www.computercraft.info
* Copyright Daniel Ratcliffe, 2011-2021. Do not distribute without permission.
* Send enquiries to dratcliffe@gmail.com
*/
package dan200.computercraft.client.proxy;
import dan200.computercraft.ComputerCraft;
import dan200.computercraft.client.gui.*;
import dan200.computercraft.client.render.TileEntityMonitorRenderer;
import dan200.computercraft.client.render.TileEntityTurtleRenderer;
import dan200.computercraft.client.render.TurtlePlayerRenderer;
import dan200.computercraft.shared.Registry;
import dan200.computercraft.shared.common.IColouredItem;
import dan200.computercraft.shared.computer.inventory.ContainerComputer;
import dan200.computercraft.shared.computer.inventory.ContainerViewComputer;
import dan200.computercraft.shared.peripheral.monitor.ClientMonitor;
import dan200.computercraft.shared.pocket.inventory.ContainerPocketComputer;
import dan200.computercraft.shared.pocket.items.ItemPocketComputer;
import net.minecraft.client.gui.ScreenManager;
import net.minecraft.client.renderer.RenderType;
import net.minecraft.client.renderer.RenderTypeLookup;
import net.minecraft.item.IItemPropertyGetter;
import net.minecraft.item.Item;
import net.minecraft.item.ItemModelsProperties;
import net.minecraft.util.ResourceLocation;
import net.minecraftforge.api.distmarker.Dist;
import net.minecraftforge.event.world.WorldEvent;
import net.minecraftforge.eventbus.api.SubscribeEvent;
import net.minecraftforge.fml.client.registry.ClientRegistry;
import net.minecraftforge.fml.client.registry.RenderingRegistry;
import net.minecraftforge.fml.common.Mod;
import net.minecraftforge.fml.event.lifecycle.FMLClientSetupEvent;
import java.util.function.Supplier;
@Mod.EventBusSubscriber( modid = ComputerCraft.MOD_ID, value = Dist.CLIENT, bus = Mod.EventBusSubscriber.Bus.MOD )
public final class ComputerCraftProxyClient
{
@SubscribeEvent
public static void setupClient( FMLClientSetupEvent event )
{
registerContainers();
// While turtles themselves are not transparent, their upgrades may be.
RenderTypeLookup.setRenderLayer( Registry.ModBlocks.TURTLE_NORMAL.get(), RenderType.translucent() );
RenderTypeLookup.setRenderLayer( Registry.ModBlocks.TURTLE_ADVANCED.get(), RenderType.translucent() );
// Monitors' textures have transparent fronts and so count as cutouts.
RenderTypeLookup.setRenderLayer( Registry.ModBlocks.MONITOR_NORMAL.get(), RenderType.cutout() );
RenderTypeLookup.setRenderLayer( Registry.ModBlocks.MONITOR_ADVANCED.get(), RenderType.cutout() );
// Setup TESRs
ClientRegistry.bindTileEntityRenderer( Registry.ModTiles.MONITOR_NORMAL.get(), TileEntityMonitorRenderer::new );
ClientRegistry.bindTileEntityRenderer( Registry.ModTiles.MONITOR_ADVANCED.get(), TileEntityMonitorRenderer::new );
ClientRegistry.bindTileEntityRenderer( Registry.ModTiles.TURTLE_NORMAL.get(), TileEntityTurtleRenderer::new );
ClientRegistry.bindTileEntityRenderer( Registry.ModTiles.TURTLE_ADVANCED.get(), TileEntityTurtleRenderer::new );
RenderingRegistry.registerEntityRenderingHandler( Registry.ModEntities.TURTLE_PLAYER.get(), TurtlePlayerRenderer::new );
registerItemProperty( "state",
( stack, world, player ) -> ItemPocketComputer.getState( stack ).ordinal(),
Registry.ModItems.POCKET_COMPUTER_NORMAL, Registry.ModItems.POCKET_COMPUTER_ADVANCED
);
registerItemProperty( "state",
( stack, world, player ) -> IColouredItem.getColourBasic( stack ) != -1 ? 1 : 0,
Registry.ModItems.POCKET_COMPUTER_NORMAL, Registry.ModItems.POCKET_COMPUTER_ADVANCED
);
}
@SafeVarargs
private static void registerItemProperty( String name, IItemPropertyGetter getter, Supplier<? extends Item>... items )
{
ResourceLocation id = new ResourceLocation( ComputerCraft.MOD_ID, name );
for( Supplier<? extends Item> item : items )
{
ItemModelsProperties.register( item.get(), id, getter );
}
}
private static void registerContainers()
{
// My IDE doesn't think so, but we do actually need these generics.
ScreenManager.<ContainerComputer, GuiComputer<ContainerComputer>>register( Registry.ModContainers.COMPUTER.get(), GuiComputer::create );
ScreenManager.<ContainerPocketComputer, GuiComputer<ContainerPocketComputer>>register( Registry.ModContainers.POCKET_COMPUTER.get(), GuiComputer::createPocket );
ScreenManager.register( Registry.ModContainers.TURTLE.get(), GuiTurtle::new );
ScreenManager.register( Registry.ModContainers.PRINTER.get(), GuiPrinter::new );
ScreenManager.register( Registry.ModContainers.DISK_DRIVE.get(), GuiDiskDrive::new );
ScreenManager.register( Registry.ModContainers.PRINTOUT.get(), GuiPrintout::new );
ScreenManager.<ContainerViewComputer, GuiComputer<ContainerViewComputer>>register( Registry.ModContainers.VIEW_COMPUTER.get(), GuiComputer::createView );
}
@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().isClientSide() )
{
ClientMonitor.destroyAll();
}
}
}
}

View File

@ -11,12 +11,19 @@ import dan200.computercraft.core.apis.http.options.AddressRule;
import dan200.computercraft.core.apis.http.options.Options; import dan200.computercraft.core.apis.http.options.Options;
import dan200.computercraft.shared.util.ThreadUtils; import dan200.computercraft.shared.util.ThreadUtils;
import io.netty.buffer.ByteBuf; import io.netty.buffer.ByteBuf;
import io.netty.channel.ConnectTimeoutException;
import io.netty.channel.EventLoopGroup; import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup; import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.handler.codec.DecoderException;
import io.netty.handler.codec.TooLongFrameException;
import io.netty.handler.codec.http.websocketx.WebSocketHandshakeException;
import io.netty.handler.ssl.SslContext; import io.netty.handler.ssl.SslContext;
import io.netty.handler.ssl.SslContextBuilder; import io.netty.handler.ssl.SslContextBuilder;
import io.netty.handler.timeout.ReadTimeoutException;
import javax.annotation.Nonnull;
import javax.net.ssl.SSLException; import javax.net.ssl.SSLException;
import javax.net.ssl.SSLHandshakeException;
import javax.net.ssl.TrustManagerFactory; import javax.net.ssl.TrustManagerFactory;
import java.net.InetSocketAddress; import java.net.InetSocketAddress;
import java.net.URI; import java.net.URI;
@ -161,4 +168,29 @@ public final class NetworkUtils
buffer.readBytes( bytes ); buffer.readBytes( bytes );
return bytes; return bytes;
} }
@Nonnull
public static String toFriendlyError( @Nonnull Throwable cause )
{
if( cause instanceof WebSocketHandshakeException || cause instanceof HTTPRequestException )
{
return cause.getMessage();
}
else if( cause instanceof TooLongFrameException )
{
return "Message is too large";
}
else if( cause instanceof ReadTimeoutException || cause instanceof ConnectTimeoutException )
{
return "Timed out";
}
else if( cause instanceof SSLHandshakeException || (cause instanceof DecoderException && cause.getCause() instanceof SSLHandshakeException) )
{
return "Could not create a secure connection";
}
else
{
return "Could not connect";
}
}
} }

View File

@ -19,13 +19,10 @@ import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelFuture; import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelInitializer; import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelPipeline; import io.netty.channel.ChannelPipeline;
import io.netty.channel.ConnectTimeoutException;
import io.netty.channel.socket.SocketChannel; import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioSocketChannel; import io.netty.channel.socket.nio.NioSocketChannel;
import io.netty.handler.codec.TooLongFrameException;
import io.netty.handler.codec.http.*; import io.netty.handler.codec.http.*;
import io.netty.handler.ssl.SslContext; import io.netty.handler.ssl.SslContext;
import io.netty.handler.timeout.ReadTimeoutException;
import io.netty.handler.timeout.ReadTimeoutHandler; import io.netty.handler.timeout.ReadTimeoutHandler;
import java.net.InetSocketAddress; import java.net.InetSocketAddress;
@ -190,7 +187,7 @@ public class HttpRequest extends Resource<HttpRequest>
.remoteAddress( socketAddress ) .remoteAddress( socketAddress )
.connect() .connect()
.addListener( c -> { .addListener( c -> {
if( !c.isSuccess() ) failure( c.cause() ); if( !c.isSuccess() ) failure( NetworkUtils.toFriendlyError( c.cause() ) );
} ); } );
// Do an additional check for cancellation // Do an additional check for cancellation
@ -202,7 +199,7 @@ public class HttpRequest extends Resource<HttpRequest>
} }
catch( Exception e ) catch( Exception e )
{ {
failure( "Could not connect" ); failure( NetworkUtils.toFriendlyError( e ) );
if( ComputerCraft.logComputerErrors ) ComputerCraft.log.error( "Error in HTTP request", e ); if( ComputerCraft.logComputerErrors ) ComputerCraft.log.error( "Error in HTTP request", e );
} }
} }
@ -212,29 +209,6 @@ public class HttpRequest extends Resource<HttpRequest>
if( tryClose() ) environment.queueEvent( FAILURE_EVENT, address, message ); if( tryClose() ) environment.queueEvent( FAILURE_EVENT, 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, HttpResponseHandle object ) void failure( String message, HttpResponseHandle object )
{ {
if( tryClose() ) environment.queueEvent( FAILURE_EVENT, address, message, object ); if( tryClose() ) environment.queueEvent( FAILURE_EVENT, address, message, object );

View File

@ -183,7 +183,7 @@ public final class HttpRequestHandler extends SimpleChannelInboundHandler<HttpOb
public void exceptionCaught( ChannelHandlerContext ctx, Throwable cause ) public void exceptionCaught( ChannelHandlerContext ctx, Throwable cause )
{ {
if( ComputerCraft.logComputerErrors ) ComputerCraft.log.error( "Error handling HTTP response", cause ); if( ComputerCraft.logComputerErrors ) ComputerCraft.log.error( "Error handling HTTP response", cause );
request.failure( cause ); request.failure( NetworkUtils.toFriendlyError( cause ) );
} }
private void sendResponse() private void sendResponse()

View File

@ -166,7 +166,7 @@ public class Websocket extends Resource<Websocket>
.remoteAddress( socketAddress ) .remoteAddress( socketAddress )
.connect() .connect()
.addListener( c -> { .addListener( c -> {
if( !c.isSuccess() ) failure( c.cause().getMessage() ); if( !c.isSuccess() ) failure( NetworkUtils.toFriendlyError( c.cause() ) );
} ); } );
// Do an additional check for cancellation // Do an additional check for cancellation
@ -178,7 +178,7 @@ public class Websocket extends Resource<Websocket>
} }
catch( Exception e ) catch( Exception e )
{ {
failure( "Could not connect" ); failure( NetworkUtils.toFriendlyError( e ) );
if( ComputerCraft.logComputerErrors ) ComputerCraft.log.error( "Error in websocket", e ); if( ComputerCraft.logComputerErrors ) ComputerCraft.log.error( "Error in websocket", e );
} }
} }

View File

@ -5,17 +5,13 @@
*/ */
package dan200.computercraft.core.apis.http.websocket; 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.apis.http.NetworkUtils;
import dan200.computercraft.core.apis.http.options.Options; import dan200.computercraft.core.apis.http.options.Options;
import dan200.computercraft.core.tracking.TrackingField; import dan200.computercraft.core.tracking.TrackingField;
import io.netty.channel.ChannelHandlerContext; import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ConnectTimeoutException;
import io.netty.channel.SimpleChannelInboundHandler; import io.netty.channel.SimpleChannelInboundHandler;
import io.netty.handler.codec.TooLongFrameException;
import io.netty.handler.codec.http.FullHttpResponse; import io.netty.handler.codec.http.FullHttpResponse;
import io.netty.handler.codec.http.websocketx.*; import io.netty.handler.codec.http.websocketx.*;
import io.netty.handler.timeout.ReadTimeoutException;
import io.netty.util.CharsetUtil; import io.netty.util.CharsetUtil;
import static dan200.computercraft.core.apis.http.websocket.Websocket.MESSAGE_EVENT; import static dan200.computercraft.core.apis.http.websocket.Websocket.MESSAGE_EVENT;
@ -97,24 +93,7 @@ public class WebsocketHandler extends SimpleChannelInboundHandler<Object>
{ {
ctx.close(); ctx.close();
String message; String message = NetworkUtils.toFriendlyError( cause );
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() ) if( handshaker.isHandshakeComplete() )
{ {
websocket.close( -1, message ); websocket.close( -1, message );

View File

@ -5,7 +5,7 @@
*/ */
package dan200.computercraft.data; package dan200.computercraft.data;
import dan200.computercraft.shared.proxy.ComputerCraftProxyCommon; import dan200.computercraft.shared.Registry;
import net.minecraft.data.DataGenerator; import net.minecraft.data.DataGenerator;
import net.minecraftforge.eventbus.api.SubscribeEvent; import net.minecraftforge.eventbus.api.SubscribeEvent;
import net.minecraftforge.fml.common.Mod; import net.minecraftforge.fml.common.Mod;
@ -17,7 +17,7 @@ public class Generators
@SubscribeEvent @SubscribeEvent
public static void gather( GatherDataEvent event ) public static void gather( GatherDataEvent event )
{ {
ComputerCraftProxyCommon.registerLoot(); Registry.registerLoot();
DataGenerator generator = event.getGenerator(); DataGenerator generator = event.getGenerator();
generator.addProvider( new Recipes( generator ) ); generator.addProvider( new Recipes( generator ) );

View File

@ -10,7 +10,7 @@ import dan200.computercraft.shared.Registry;
import dan200.computercraft.shared.data.BlockNamedEntityLootCondition; import dan200.computercraft.shared.data.BlockNamedEntityLootCondition;
import dan200.computercraft.shared.data.HasComputerIdLootCondition; import dan200.computercraft.shared.data.HasComputerIdLootCondition;
import dan200.computercraft.shared.data.PlayerCreativeLootCondition; import dan200.computercraft.shared.data.PlayerCreativeLootCondition;
import dan200.computercraft.shared.proxy.ComputerCraftProxyCommon; import dan200.computercraft.shared.CommonHooks;
import net.minecraft.block.Block; import net.minecraft.block.Block;
import net.minecraft.data.DataGenerator; import net.minecraft.data.DataGenerator;
import net.minecraft.loot.*; import net.minecraft.loot.*;
@ -46,7 +46,7 @@ public class LootTables extends LootTableProvider
computerDrop( add, Registry.ModBlocks.TURTLE_NORMAL ); computerDrop( add, Registry.ModBlocks.TURTLE_NORMAL );
computerDrop( add, Registry.ModBlocks.TURTLE_ADVANCED ); computerDrop( add, Registry.ModBlocks.TURTLE_ADVANCED );
add.accept( ComputerCraftProxyCommon.ForgeHandlers.LOOT_TREASURE_DISK, LootTable add.accept( CommonHooks.LOOT_TREASURE_DISK, LootTable
.lootTable() .lootTable()
.setParamSet( LootParameterSets.ALL_PARAMS ) .setParamSet( LootParameterSets.ALL_PARAMS )
.build() ); .build() );

View File

@ -0,0 +1,138 @@
/*
* This file is part of ComputerCraft - http://www.computercraft.info
* Copyright Daniel Ratcliffe, 2011-2021. Do not distribute without permission.
* Send enquiries to dratcliffe@gmail.com
*/
package dan200.computercraft.shared;
import dan200.computercraft.ComputerCraft;
import dan200.computercraft.core.computer.MainThread;
import dan200.computercraft.core.tracking.ComputerMBean;
import dan200.computercraft.core.tracking.Tracking;
import dan200.computercraft.shared.command.CommandComputerCraft;
import dan200.computercraft.shared.computer.core.IComputer;
import dan200.computercraft.shared.computer.core.IContainerComputer;
import dan200.computercraft.shared.computer.core.ServerComputer;
import dan200.computercraft.shared.peripheral.modem.wireless.WirelessNetwork;
import net.minecraft.inventory.container.Container;
import net.minecraft.loot.ConstantRange;
import net.minecraft.loot.LootPool;
import net.minecraft.loot.LootTables;
import net.minecraft.loot.TableLootEntry;
import net.minecraft.server.MinecraftServer;
import net.minecraft.server.dedicated.DedicatedServer;
import net.minecraft.util.ResourceLocation;
import net.minecraftforge.event.LootTableLoadEvent;
import net.minecraftforge.event.RegisterCommandsEvent;
import net.minecraftforge.event.TickEvent;
import net.minecraftforge.event.entity.player.PlayerContainerEvent;
import net.minecraftforge.eventbus.api.SubscribeEvent;
import net.minecraftforge.fml.common.Mod;
import net.minecraftforge.fml.event.server.FMLServerStartedEvent;
import net.minecraftforge.fml.event.server.FMLServerStartingEvent;
import net.minecraftforge.fml.event.server.FMLServerStoppedEvent;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Set;
/**
* Miscellaneous hooks which are present on the client and server.
*
* These should possibly be refactored into separate classes at some point, but are fine here for now.
*
* @see dan200.computercraft.client.ClientHooks For client-specific ones.
*/
@Mod.EventBusSubscriber( modid = ComputerCraft.MOD_ID )
public final class CommonHooks
{
private CommonHooks()
{
}
@SubscribeEvent
public static void onServerTick( TickEvent.ServerTickEvent event )
{
if( event.phase == TickEvent.Phase.START )
{
MainThread.executePendingTasks();
ComputerCraft.serverComputerRegistry.update();
}
}
@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.getPlayer() );
}
}
}
@SubscribeEvent
public static void onRegisterCommand( RegisterCommandsEvent event )
{
CommandComputerCraft.register( event.getDispatcher() );
}
@SubscribeEvent
public static void onServerStarting( FMLServerStartingEvent event )
{
MinecraftServer server = event.getServer();
if( server instanceof DedicatedServer && ((DedicatedServer) server).getProperties().enableJmxMonitoring )
{
ComputerMBean.register();
}
}
@SubscribeEvent
public static void onServerStarted( FMLServerStartedEvent event )
{
ComputerCraft.serverComputerRegistry.reset();
WirelessNetwork.resetNetworks();
Tracking.reset();
ComputerMBean.registerTracker();
}
@SubscribeEvent
public static void onServerStopped( FMLServerStoppedEvent event )
{
ComputerCraft.serverComputerRegistry.reset();
WirelessNetwork.resetNetworks();
Tracking.reset();
}
public static final ResourceLocation LOOT_TREASURE_DISK = new ResourceLocation( ComputerCraft.MOD_ID, "treasure_disk" );
private static final Set<ResourceLocation> TABLES = new HashSet<>( Arrays.asList(
LootTables.SIMPLE_DUNGEON,
LootTables.ABANDONED_MINESHAFT,
LootTables.STRONGHOLD_CORRIDOR,
LootTables.STRONGHOLD_CROSSING,
LootTables.STRONGHOLD_LIBRARY,
LootTables.DESERT_PYRAMID,
LootTables.JUNGLE_TEMPLE,
LootTables.IGLOO_CHEST,
LootTables.WOODLAND_MANSION,
LootTables.VILLAGE_CARTOGRAPHER
) );
@SubscribeEvent
public static void lootLoad( LootTableLoadEvent event )
{
ResourceLocation name = event.getName();
if( !name.getNamespace().equals( "minecraft" ) || !TABLES.contains( name ) ) return;
event.getTable().addPool( LootPool.lootPool()
.add( TableLootEntry.lootTableReference( LOOT_TREASURE_DISK ) )
.setRolls( ConstantRange.exactly( 1 ) )
.name( "computercraft_treasure" )
.build() );
}
}

View File

@ -7,8 +7,13 @@ package dan200.computercraft.shared;
import dan200.computercraft.ComputerCraft; import dan200.computercraft.ComputerCraft;
import dan200.computercraft.api.ComputerCraftAPI; import dan200.computercraft.api.ComputerCraftAPI;
import dan200.computercraft.api.media.IMedia;
import dan200.computercraft.api.network.wired.IWiredElement;
import dan200.computercraft.api.peripheral.IPeripheral;
import dan200.computercraft.shared.command.arguments.ArgumentSerializers;
import dan200.computercraft.shared.common.ColourableRecipe; import dan200.computercraft.shared.common.ColourableRecipe;
import dan200.computercraft.shared.common.ContainerHeldItem; import dan200.computercraft.shared.common.ContainerHeldItem;
import dan200.computercraft.shared.common.DefaultBundledRedstoneProvider;
import dan200.computercraft.shared.computer.blocks.BlockComputer; import dan200.computercraft.shared.computer.blocks.BlockComputer;
import dan200.computercraft.shared.computer.blocks.TileCommandComputer; import dan200.computercraft.shared.computer.blocks.TileCommandComputer;
import dan200.computercraft.shared.computer.blocks.TileComputer; import dan200.computercraft.shared.computer.blocks.TileComputer;
@ -17,11 +22,16 @@ import dan200.computercraft.shared.computer.inventory.ContainerComputer;
import dan200.computercraft.shared.computer.inventory.ContainerViewComputer; import dan200.computercraft.shared.computer.inventory.ContainerViewComputer;
import dan200.computercraft.shared.computer.items.ItemComputer; import dan200.computercraft.shared.computer.items.ItemComputer;
import dan200.computercraft.shared.computer.recipe.ComputerUpgradeRecipe; import dan200.computercraft.shared.computer.recipe.ComputerUpgradeRecipe;
import dan200.computercraft.shared.data.BlockNamedEntityLootCondition;
import dan200.computercraft.shared.data.HasComputerIdLootCondition;
import dan200.computercraft.shared.data.PlayerCreativeLootCondition;
import dan200.computercraft.shared.media.items.ItemDisk; import dan200.computercraft.shared.media.items.ItemDisk;
import dan200.computercraft.shared.media.items.ItemPrintout; import dan200.computercraft.shared.media.items.ItemPrintout;
import dan200.computercraft.shared.media.items.ItemTreasureDisk; import dan200.computercraft.shared.media.items.ItemTreasureDisk;
import dan200.computercraft.shared.media.items.RecordMedia;
import dan200.computercraft.shared.media.recipes.DiskRecipe; import dan200.computercraft.shared.media.recipes.DiskRecipe;
import dan200.computercraft.shared.media.recipes.PrintoutRecipe; import dan200.computercraft.shared.media.recipes.PrintoutRecipe;
import dan200.computercraft.shared.network.NetworkHandler;
import dan200.computercraft.shared.network.container.ComputerContainerData; import dan200.computercraft.shared.network.container.ComputerContainerData;
import dan200.computercraft.shared.network.container.ContainerData; import dan200.computercraft.shared.network.container.ContainerData;
import dan200.computercraft.shared.network.container.HeldItemContainerData; import dan200.computercraft.shared.network.container.HeldItemContainerData;
@ -29,6 +39,9 @@ import dan200.computercraft.shared.network.container.ViewComputerContainerData;
import dan200.computercraft.shared.peripheral.diskdrive.BlockDiskDrive; import dan200.computercraft.shared.peripheral.diskdrive.BlockDiskDrive;
import dan200.computercraft.shared.peripheral.diskdrive.ContainerDiskDrive; import dan200.computercraft.shared.peripheral.diskdrive.ContainerDiskDrive;
import dan200.computercraft.shared.peripheral.diskdrive.TileDiskDrive; import dan200.computercraft.shared.peripheral.diskdrive.TileDiskDrive;
import dan200.computercraft.shared.peripheral.generic.methods.EnergyMethods;
import dan200.computercraft.shared.peripheral.generic.methods.FluidMethods;
import dan200.computercraft.shared.peripheral.generic.methods.InventoryMethods;
import dan200.computercraft.shared.peripheral.modem.wired.*; import dan200.computercraft.shared.peripheral.modem.wired.*;
import dan200.computercraft.shared.peripheral.modem.wireless.BlockWirelessModem; import dan200.computercraft.shared.peripheral.modem.wireless.BlockWirelessModem;
import dan200.computercraft.shared.peripheral.modem.wireless.TileWirelessModem; import dan200.computercraft.shared.peripheral.modem.wireless.TileWirelessModem;
@ -52,30 +65,31 @@ import dan200.computercraft.shared.turtle.items.ItemTurtle;
import dan200.computercraft.shared.turtle.recipes.TurtleRecipe; import dan200.computercraft.shared.turtle.recipes.TurtleRecipe;
import dan200.computercraft.shared.turtle.recipes.TurtleUpgradeRecipe; import dan200.computercraft.shared.turtle.recipes.TurtleUpgradeRecipe;
import dan200.computercraft.shared.turtle.upgrades.*; import dan200.computercraft.shared.turtle.upgrades.*;
import dan200.computercraft.shared.util.CreativeTabMain; import dan200.computercraft.shared.util.*;
import dan200.computercraft.shared.util.FixedPointTileEntityType;
import dan200.computercraft.shared.util.ImpostorRecipe;
import dan200.computercraft.shared.util.ImpostorShapelessRecipe;
import net.minecraft.block.AbstractBlock; import net.minecraft.block.AbstractBlock;
import net.minecraft.block.Block; import net.minecraft.block.Block;
import net.minecraft.block.material.Material; import net.minecraft.block.material.Material;
import net.minecraft.entity.EntityClassification; import net.minecraft.entity.EntityClassification;
import net.minecraft.entity.EntityType; import net.minecraft.entity.EntityType;
import net.minecraft.inventory.container.ContainerType; import net.minecraft.inventory.container.ContainerType;
import net.minecraft.item.BlockItem; import net.minecraft.item.*;
import net.minecraft.item.Item;
import net.minecraft.item.ItemGroup;
import net.minecraft.item.Items;
import net.minecraft.item.crafting.IRecipeSerializer; import net.minecraft.item.crafting.IRecipeSerializer;
import net.minecraft.loot.LootConditionType;
import net.minecraft.tileentity.TileEntity; import net.minecraft.tileentity.TileEntity;
import net.minecraft.tileentity.TileEntityType; import net.minecraft.tileentity.TileEntityType;
import net.minecraft.util.ResourceLocation; import net.minecraft.util.ResourceLocation;
import net.minecraftforge.common.capabilities.CapabilityManager;
import net.minecraftforge.energy.CapabilityEnergy;
import net.minecraftforge.event.RegistryEvent; import net.minecraftforge.event.RegistryEvent;
import net.minecraftforge.eventbus.api.IEventBus; import net.minecraftforge.eventbus.api.IEventBus;
import net.minecraftforge.eventbus.api.SubscribeEvent; import net.minecraftforge.eventbus.api.SubscribeEvent;
import net.minecraftforge.fluids.capability.CapabilityFluidHandler;
import net.minecraftforge.fml.DeferredWorkQueue;
import net.minecraftforge.fml.RegistryObject; import net.minecraftforge.fml.RegistryObject;
import net.minecraftforge.fml.common.Mod; import net.minecraftforge.fml.common.Mod;
import net.minecraftforge.fml.event.lifecycle.FMLCommonSetupEvent;
import net.minecraftforge.fml.javafmlmod.FMLJavaModLoadingContext; import net.minecraftforge.fml.javafmlmod.FMLJavaModLoadingContext;
import net.minecraftforge.items.CapabilityItemHandler;
import net.minecraftforge.registries.DeferredRegister; import net.minecraftforge.registries.DeferredRegister;
import net.minecraftforge.registries.ForgeRegistries; import net.minecraftforge.registries.ForgeRegistries;
@ -334,6 +348,62 @@ public final class Registry
); );
} }
@SubscribeEvent
@SuppressWarnings( "deprecation" )
public static void init( FMLCommonSetupEvent event )
{
NetworkHandler.setup();
DeferredWorkQueue.runLater( () -> {
registerProviders();
ArgumentSerializers.register();
registerLoot();
} );
ComputerCraftAPI.registerGenericSource( new InventoryMethods() );
ComputerCraftAPI.registerGenericSource( new FluidMethods() );
ComputerCraftAPI.registerGenericSource( new EnergyMethods() );
}
private static void registerProviders()
{
// 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;
} );
// Register capabilities
CapabilityManager.INSTANCE.register( IWiredElement.class, new NullStorage<>(), () -> null );
CapabilityManager.INSTANCE.register( IPeripheral.class, new NullStorage<>(), () -> null );
// Register generic capabilities. This can technically be done off-thread, but we need it to happen
// after Forge's common setup, so this is easiest.
ComputerCraftAPI.registerGenericCapability( CapabilityItemHandler.ITEM_HANDLER_CAPABILITY );
ComputerCraftAPI.registerGenericCapability( CapabilityEnergy.ENERGY );
ComputerCraftAPI.registerGenericCapability( CapabilityFluidHandler.FLUID_HANDLER_CAPABILITY );
}
public static void registerLoot()
{
registerCondition( "block_named", BlockNamedEntityLootCondition.TYPE );
registerCondition( "player_creative", PlayerCreativeLootCondition.TYPE );
registerCondition( "has_id", HasComputerIdLootCondition.TYPE );
}
private static void registerCondition( String name, LootConditionType serializer )
{
net.minecraft.util.registry.Registry.register(
net.minecraft.util.registry.Registry.LOOT_CONDITION_TYPE,
new ResourceLocation( ComputerCraft.MOD_ID, name ), serializer
);
}
public static void setup() public static void setup()
{ {
IEventBus bus = FMLJavaModLoadingContext.get().getModEventBus(); IEventBus bus = FMLJavaModLoadingContext.get().getModEventBus();

View File

@ -0,0 +1,58 @@
/*
* This file is part of ComputerCraft - http://www.computercraft.info
* Copyright Daniel Ratcliffe, 2011-2021. Do not distribute without permission.
* Send enquiries to dratcliffe@gmail.com
*/
package dan200.computercraft.shared.command;
import dan200.computercraft.ComputerCraft;
import dan200.computercraft.shared.util.IDAssigner;
import net.minecraft.util.Util;
import net.minecraftforge.api.distmarker.Dist;
import net.minecraftforge.client.event.ClientChatEvent;
import net.minecraftforge.eventbus.api.SubscribeEvent;
import net.minecraftforge.fml.common.Mod;
import java.io.File;
/**
* Basic client-side commands.
*
* Simply hooks into client chat messages and intercepts matching strings.
*/
@Mod.EventBusSubscriber( modid = ComputerCraft.MOD_ID, value = Dist.CLIENT )
public final class ClientCommands
{
public static final String OPEN_COMPUTER = "/computercraft open-computer ";
private ClientCommands()
{
}
@SubscribeEvent
public static void onClientSendMessage( ClientChatEvent event )
{
// Emulate the command on the client side
if( event.getMessage().startsWith( OPEN_COMPUTER ) )
{
event.setCanceled( true );
String idStr = event.getMessage().substring( OPEN_COMPUTER.length() ).trim();
int id;
try
{
id = Integer.parseInt( idStr );
}
catch( NumberFormatException ignore )
{
return;
}
File file = new File( IDAssigner.getDir(), "computer/" + id );
if( !file.isDirectory() ) return;
Util.getPlatform().openFile( file );
}
}
}

View File

@ -21,6 +21,7 @@ import dan200.computercraft.shared.computer.core.ComputerFamily;
import dan200.computercraft.shared.computer.core.ServerComputer; import dan200.computercraft.shared.computer.core.ServerComputer;
import dan200.computercraft.shared.computer.inventory.ContainerViewComputer; import dan200.computercraft.shared.computer.inventory.ContainerViewComputer;
import dan200.computercraft.shared.network.container.ViewComputerContainerData; import dan200.computercraft.shared.network.container.ViewComputerContainerData;
import dan200.computercraft.shared.util.IDAssigner;
import net.minecraft.command.CommandSource; import net.minecraft.command.CommandSource;
import net.minecraft.entity.Entity; import net.minecraft.entity.Entity;
import net.minecraft.entity.player.PlayerEntity; import net.minecraft.entity.player.PlayerEntity;
@ -38,6 +39,7 @@ import net.minecraft.world.World;
import net.minecraft.world.server.ServerWorld; import net.minecraft.world.server.ServerWorld;
import javax.annotation.Nonnull; import javax.annotation.Nonnull;
import java.io.File;
import java.util.*; import java.util.*;
import static dan200.computercraft.shared.command.CommandUtils.isPlayer; import static dan200.computercraft.shared.command.CommandUtils.isPlayer;
@ -321,6 +323,12 @@ public final class CommandComputerCraft
) ); ) );
} }
if( UserLevel.OWNER.test( source ) && isPlayer( source ) )
{
ITextComponent linkPath = linkStorage( computerId );
if( linkPath != null ) out.append( " " ).append( linkPath );
}
return out; return out;
} }
@ -340,6 +348,18 @@ public final class CommandComputerCraft
} }
} }
private static ITextComponent linkStorage( int id )
{
File file = new File( IDAssigner.getDir(), "computer/" + id );
if( !file.isDirectory() ) return null;
return link(
text( "\u270E" ),
ClientCommands.OPEN_COMPUTER + id,
translate( "commands.computercraft.dump.open_path" )
);
}
@Nonnull @Nonnull
private static TrackingContext getTimingContext( CommandSource source ) private static TrackingContext getTimingContext( CommandSource source )
{ {

View File

@ -1,65 +0,0 @@
/*
* This file is part of ComputerCraft - http://www.computercraft.info
* Copyright Daniel Ratcliffe, 2011-2021. Do not distribute without permission.
* Send enquiries to dratcliffe@gmail.com
*/
package dan200.computercraft.shared.command;
import com.mojang.brigadier.CommandDispatcher;
import com.mojang.brigadier.arguments.StringArgumentType;
import dan200.computercraft.ComputerCraft;
import net.minecraft.client.Minecraft;
import net.minecraft.command.CommandSource;
import net.minecraft.util.text.ITextComponent;
import net.minecraft.util.text.StringTextComponent;
import net.minecraft.util.text.Style;
import net.minecraft.util.text.TranslationTextComponent;
import net.minecraft.util.text.event.ClickEvent;
import net.minecraft.util.text.event.HoverEvent;
import net.minecraftforge.api.distmarker.Dist;
import net.minecraftforge.client.event.ClientChatEvent;
import net.minecraftforge.eventbus.api.SubscribeEvent;
import net.minecraftforge.fml.common.Mod;
import static net.minecraft.command.Commands.argument;
import static net.minecraft.command.Commands.literal;
@Mod.EventBusSubscriber( modid = ComputerCraft.MOD_ID, value = Dist.CLIENT )
public final class CommandCopy
{
private static final String PREFIX = "/computercraft copy ";
private CommandCopy()
{
}
public static void register( CommandDispatcher<CommandSource> registry )
{
registry.register( literal( "computercraft" )
.then( literal( "copy" ) )
.then( argument( "message", StringArgumentType.greedyString() ) )
.executes( context -> {
Minecraft.getInstance().keyboardHandler.setClipboard( context.getArgument( "message", String.class ) );
return 1;
} )
);
}
@SubscribeEvent
public static void onClientSendMessage( ClientChatEvent event )
{
// Emulate the command on the client side
if( event.getMessage().startsWith( PREFIX ) )
{
Minecraft.getInstance().keyboardHandler.setClipboard( event.getMessage().substring( PREFIX.length() ) );
event.setCanceled( true );
}
}
public static ITextComponent createCopyText( String text )
{
return new StringTextComponent( text ).withStyle( Style.EMPTY
.withClickEvent( new ClickEvent( ClickEvent.Action.RUN_COMMAND, PREFIX + text ) )
.withHoverEvent( new HoverEvent( HoverEvent.Action.SHOW_TEXT, new TranslationTextComponent( "gui.computercraft.tooltip.copy" ) ) ) );
}
}

View File

@ -57,14 +57,15 @@ public enum UserLevel implements Predicate<CommandSource>
{ {
if( this == ANYONE ) return true; if( this == ANYONE ) return true;
// We *always* allow level 0 stuff, even if the if( this == OWNER || this == OWNER_OP )
{
MinecraftServer server = source.getServer(); MinecraftServer server = source.getServer();
Entity sender = source.getEntity(); Entity sender = source.getEntity();
if( server.isSingleplayer() && sender instanceof PlayerEntity && if( server.isSingleplayer() && sender instanceof PlayerEntity &&
((PlayerEntity) sender).getGameProfile().getName().equalsIgnoreCase( server.getServerModName() ) ) ((PlayerEntity) sender).getGameProfile().getName().equalsIgnoreCase( server.getServerModName() ) )
{ {
if( this == OWNER || this == OWNER_OP ) return true; return true;
}
} }
return source.hasPermission( toLevel() ); return source.hasPermission( toLevel() );

View File

@ -68,19 +68,33 @@ public final class ChatHelpers
: coloured( translate( "commands.computercraft.generic.no" ), TextFormatting.RED ); : coloured( translate( "commands.computercraft.generic.no" ), TextFormatting.RED );
} }
public static IFormattableTextComponent link( IFormattableTextComponent component, String command, ITextComponent toolTip ) public static ITextComponent link( IFormattableTextComponent component, String command, ITextComponent toolTip )
{
return link( component, new ClickEvent( ClickEvent.Action.RUN_COMMAND, command ), toolTip );
}
public static ITextComponent link( ITextComponent component, ClickEvent click, ITextComponent toolTip )
{ {
Style style = component.getStyle(); Style style = component.getStyle();
if( style.getColor() == null ) style = style.withColor( TextFormatting.YELLOW ); if( style.getColor() == null ) style = style.withColor( TextFormatting.YELLOW );
style = style.withClickEvent( new ClickEvent( ClickEvent.Action.RUN_COMMAND, command ) ); style = style.withClickEvent( click );
style = style.withHoverEvent( new HoverEvent( HoverEvent.Action.SHOW_TEXT, toolTip ) ); style = style.withHoverEvent( new HoverEvent( HoverEvent.Action.SHOW_TEXT, toolTip ) );
return component.setStyle( style ); return component.copy().withStyle( style );
} }
public static IFormattableTextComponent header( String text ) public static IFormattableTextComponent header( String text )
{ {
return coloured( text, HEADER ); return coloured( text, HEADER );
} }
public static IFormattableTextComponent copy( String text )
{
StringTextComponent name = new StringTextComponent( text );
Style style = name.getStyle()
.withClickEvent( new ClickEvent( ClickEvent.Action.COPY_TO_CLIPBOARD, text ) )
.withHoverEvent( new HoverEvent( HoverEvent.Action.SHOW_TEXT, new TranslationTextComponent( "gui.computercraft.tooltip.copy" ) ) );
return name.withStyle( style );
}
} }

View File

@ -57,7 +57,7 @@ public class TileCommandComputer extends TileComputer
@Override @Override
public boolean acceptsSuccess() public boolean acceptsSuccess()
{ {
return getLevel().getGameRules().getBoolean( GameRules.RULE_SENDCOMMANDFEEDBACK ); return true;
} }
@Override @Override

View File

@ -175,12 +175,12 @@ public abstract class TileComputerBase extends TileGeneric implements IComputerT
label = computer.getLabel(); label = computer.getLabel();
on = computer.isOn(); on = computer.isOn();
if( computer.hasOutputChanged() ) updateOutput();
// Update the block state if needed. We don't fire a block update intentionally, // Update the block state if needed. We don't fire a block update intentionally,
// as this only really is needed on the client side. // as this only really is needed on the client side.
updateBlockState( computer.getState() ); updateBlockState( computer.getState() );
// TODO: This should ideally be split up into label/id/on (which should save NBT and sync to client) and
// redstone (which should update outputs)
if( computer.hasOutputChanged() ) updateOutput(); if( computer.hasOutputChanged() ) updateOutput();
} }
} }
@ -376,11 +376,8 @@ public abstract class TileComputerBase extends TileGeneric implements IComputerT
fresh = true; fresh = true;
changed = true; changed = true;
} }
if( changed )
{ if( changed ) updateInput();
updateBlock();
updateInput();
}
return ComputerCraft.serverComputerRegistry.get( instanceID ); return ComputerCraft.serverComputerRegistry.get( instanceID );
} }

View File

@ -68,6 +68,8 @@ public class ItemTreasureDisk extends Item implements IMedia
public IMount createDataMount( @Nonnull ItemStack stack, @Nonnull World world ) public IMount createDataMount( @Nonnull ItemStack stack, @Nonnull World world )
{ {
IMount rootTreasure = getTreasureMount(); IMount rootTreasure = getTreasureMount();
if( rootTreasure == null ) return null;
String subPath = getSubPath( stack ); String subPath = getSubPath( stack );
try try
{ {
@ -121,7 +123,7 @@ public class ItemTreasureDisk extends Item implements IMedia
private static String getTitle( @Nonnull ItemStack stack ) private static String getTitle( @Nonnull ItemStack stack )
{ {
CompoundNBT nbt = stack.getTag(); CompoundNBT nbt = stack.getTag();
return nbt != null && nbt.contains( NBT_TITLE ) ? nbt.getString( NBT_TITLE ) : "'alongtimeago' by dan200"; return nbt != null && nbt.contains( NBT_TITLE ) ? nbt.getString( NBT_TITLE ) : "'missingno' by how did you get this anyway?";
} }
@Nonnull @Nonnull

View File

@ -11,7 +11,7 @@ import dan200.computercraft.api.network.wired.IWiredElement;
import dan200.computercraft.api.network.wired.IWiredNode; import dan200.computercraft.api.network.wired.IWiredNode;
import dan200.computercraft.api.peripheral.IPeripheral; import dan200.computercraft.api.peripheral.IPeripheral;
import dan200.computercraft.shared.Registry; import dan200.computercraft.shared.Registry;
import dan200.computercraft.shared.command.CommandCopy; import dan200.computercraft.shared.command.text.ChatHelpers;
import dan200.computercraft.shared.common.TileGeneric; import dan200.computercraft.shared.common.TileGeneric;
import dan200.computercraft.shared.peripheral.modem.ModemState; import dan200.computercraft.shared.peripheral.modem.ModemState;
import dan200.computercraft.shared.util.CapabilityUtil; import dan200.computercraft.shared.util.CapabilityUtil;
@ -268,12 +268,12 @@ public class TileCable extends TileGeneric
if( oldName != null ) if( oldName != null )
{ {
player.displayClientMessage( new TranslationTextComponent( "chat.computercraft.wired_modem.peripheral_disconnected", player.displayClientMessage( new TranslationTextComponent( "chat.computercraft.wired_modem.peripheral_disconnected",
CommandCopy.createCopyText( oldName ) ), false ); ChatHelpers.copy( oldName ) ), false );
} }
if( newName != null ) if( newName != null )
{ {
player.displayClientMessage( new TranslationTextComponent( "chat.computercraft.wired_modem.peripheral_connected", player.displayClientMessage( new TranslationTextComponent( "chat.computercraft.wired_modem.peripheral_connected",
CommandCopy.createCopyText( newName ) ), false ); ChatHelpers.copy( newName ) ), false );
} }
} }

View File

@ -10,7 +10,7 @@ import dan200.computercraft.api.ComputerCraftAPI;
import dan200.computercraft.api.network.wired.IWiredElement; import dan200.computercraft.api.network.wired.IWiredElement;
import dan200.computercraft.api.network.wired.IWiredNode; import dan200.computercraft.api.network.wired.IWiredNode;
import dan200.computercraft.api.peripheral.IPeripheral; import dan200.computercraft.api.peripheral.IPeripheral;
import dan200.computercraft.shared.command.CommandCopy; import dan200.computercraft.shared.command.text.ChatHelpers;
import dan200.computercraft.shared.common.TileGeneric; import dan200.computercraft.shared.common.TileGeneric;
import dan200.computercraft.shared.peripheral.modem.ModemState; import dan200.computercraft.shared.peripheral.modem.ModemState;
import dan200.computercraft.shared.util.CapabilityUtil; import dan200.computercraft.shared.util.CapabilityUtil;
@ -218,7 +218,7 @@ public class TileWiredModemFull extends TileGeneric
for( int i = 0; i < names.size(); i++ ) for( int i = 0; i < names.size(); i++ )
{ {
if( i > 0 ) base.append( ", " ); if( i > 0 ) base.append( ", " );
base.append( CommandCopy.createCopyText( names.get( i ) ) ); base.append( ChatHelpers.copy( names.get( i ) ) );
} }
player.displayClientMessage( new TranslationTextComponent( kind, base ), false ); player.displayClientMessage( new TranslationTextComponent( kind, base ), false );

View File

@ -279,8 +279,6 @@ public class TileMonitor extends TileGeneric
int oldXIndex = xIndex; int oldXIndex = xIndex;
int oldYIndex = yIndex; int oldYIndex = yIndex;
int oldWidth = width;
int oldHeight = height;
xIndex = nbt.getInt( NBT_X ); xIndex = nbt.getInt( NBT_X );
yIndex = nbt.getInt( NBT_Y ); yIndex = nbt.getInt( NBT_Y );
@ -300,13 +298,6 @@ public class TileMonitor extends TileGeneric
// If we're the origin terminal then create it. // If we're the origin terminal then create it.
if( clientMonitor == null ) clientMonitor = new ClientMonitor( advanced, this ); if( clientMonitor == null ) clientMonitor = new ClientMonitor( advanced, this );
} }
if( oldXIndex != xIndex || oldYIndex != yIndex ||
oldWidth != width || oldHeight != height )
{
// One of our properties has changed, so ensure we redraw the block
updateBlock();
}
} }
public final void read( TerminalState state ) public final void read( TerminalState state )

View File

@ -1,223 +0,0 @@
/*
* This file is part of ComputerCraft - http://www.computercraft.info
* Copyright Daniel Ratcliffe, 2011-2021. Do not distribute without permission.
* Send enquiries to dratcliffe@gmail.com
*/
package dan200.computercraft.shared.proxy;
import dan200.computercraft.ComputerCraft;
import dan200.computercraft.api.ComputerCraftAPI;
import dan200.computercraft.api.media.IMedia;
import dan200.computercraft.api.network.wired.IWiredElement;
import dan200.computercraft.api.peripheral.IPeripheral;
import dan200.computercraft.core.computer.MainThread;
import dan200.computercraft.core.tracking.ComputerMBean;
import dan200.computercraft.core.tracking.Tracking;
import dan200.computercraft.shared.command.CommandComputerCraft;
import dan200.computercraft.shared.command.arguments.ArgumentSerializers;
import dan200.computercraft.shared.common.DefaultBundledRedstoneProvider;
import dan200.computercraft.shared.computer.core.IComputer;
import dan200.computercraft.shared.computer.core.IContainerComputer;
import dan200.computercraft.shared.computer.core.ServerComputer;
import dan200.computercraft.shared.data.BlockNamedEntityLootCondition;
import dan200.computercraft.shared.data.HasComputerIdLootCondition;
import dan200.computercraft.shared.data.PlayerCreativeLootCondition;
import dan200.computercraft.shared.media.items.RecordMedia;
import dan200.computercraft.shared.network.NetworkHandler;
import dan200.computercraft.shared.peripheral.generic.methods.EnergyMethods;
import dan200.computercraft.shared.peripheral.generic.methods.FluidMethods;
import dan200.computercraft.shared.peripheral.generic.methods.InventoryMethods;
import dan200.computercraft.shared.peripheral.modem.wireless.WirelessNetwork;
import dan200.computercraft.shared.util.NullStorage;
import net.minecraft.inventory.container.Container;
import net.minecraft.item.Item;
import net.minecraft.item.MusicDiscItem;
import net.minecraft.loot.*;
import net.minecraft.server.MinecraftServer;
import net.minecraft.server.dedicated.DedicatedServer;
import net.minecraft.util.ResourceLocation;
import net.minecraft.util.registry.Registry;
import net.minecraftforge.common.capabilities.CapabilityManager;
import net.minecraftforge.energy.CapabilityEnergy;
import net.minecraftforge.event.LootTableLoadEvent;
import net.minecraftforge.event.RegisterCommandsEvent;
import net.minecraftforge.event.TickEvent;
import net.minecraftforge.event.entity.player.PlayerContainerEvent;
import net.minecraftforge.eventbus.api.SubscribeEvent;
import net.minecraftforge.fluids.capability.CapabilityFluidHandler;
import net.minecraftforge.fml.DeferredWorkQueue;
import net.minecraftforge.fml.common.Mod;
import net.minecraftforge.fml.event.lifecycle.FMLCommonSetupEvent;
import net.minecraftforge.fml.event.server.FMLServerStartedEvent;
import net.minecraftforge.fml.event.server.FMLServerStartingEvent;
import net.minecraftforge.fml.event.server.FMLServerStoppedEvent;
import net.minecraftforge.items.CapabilityItemHandler;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Set;
@Mod.EventBusSubscriber( modid = ComputerCraft.MOD_ID, bus = Mod.EventBusSubscriber.Bus.MOD )
public final class ComputerCraftProxyCommon
{
@SubscribeEvent
@SuppressWarnings( "deprecation" )
public static void init( FMLCommonSetupEvent event )
{
NetworkHandler.setup();
DeferredWorkQueue.runLater( () -> {
registerProviders();
ArgumentSerializers.register();
registerLoot();
} );
ComputerCraftAPI.registerGenericSource( new InventoryMethods() );
ComputerCraftAPI.registerGenericSource( new FluidMethods() );
ComputerCraftAPI.registerGenericSource( new EnergyMethods() );
}
public static void registerLoot()
{
registerCondition( "block_named", BlockNamedEntityLootCondition.TYPE );
registerCondition( "player_creative", PlayerCreativeLootCondition.TYPE );
registerCondition( "has_id", HasComputerIdLootCondition.TYPE );
}
private static void registerCondition( String name, LootConditionType serializer )
{
Registry.register( Registry.LOOT_CONDITION_TYPE, new ResourceLocation( ComputerCraft.MOD_ID, name ), serializer );
}
private static void registerProviders()
{
// 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;
} );
// Register capabilities
CapabilityManager.INSTANCE.register( IWiredElement.class, new NullStorage<>(), () -> null );
CapabilityManager.INSTANCE.register( IPeripheral.class, new NullStorage<>(), () -> null );
// Register generic capabilities. This can technically be done off-thread, but we need it to happen
// after Forge's common setup, so this is easiest.
ComputerCraftAPI.registerGenericCapability( CapabilityItemHandler.ITEM_HANDLER_CAPABILITY );
ComputerCraftAPI.registerGenericCapability( CapabilityEnergy.ENERGY );
ComputerCraftAPI.registerGenericCapability( CapabilityFluidHandler.FLUID_HANDLER_CAPABILITY );
}
@Mod.EventBusSubscriber( modid = ComputerCraft.MOD_ID )
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 onServerTick( TickEvent.ServerTickEvent event )
{
if( event.phase == TickEvent.Phase.START )
{
MainThread.executePendingTasks();
ComputerCraft.serverComputerRegistry.update();
}
}
@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.getPlayer() );
}
}
}
@SubscribeEvent
public static void onRegisterCommand( RegisterCommandsEvent event )
{
CommandComputerCraft.register( event.getDispatcher() );
}
@SubscribeEvent
public static void onServerStarting( FMLServerStartingEvent event )
{
MinecraftServer server = event.getServer();
if( server instanceof DedicatedServer && ((DedicatedServer) server).getProperties().enableJmxMonitoring )
{
ComputerMBean.register();
}
}
@SubscribeEvent
public static void onServerStarted( FMLServerStartedEvent event )
{
ComputerCraft.serverComputerRegistry.reset();
WirelessNetwork.resetNetworks();
Tracking.reset();
ComputerMBean.registerTracker();
}
@SubscribeEvent
public static void onServerStopped( FMLServerStoppedEvent event )
{
ComputerCraft.serverComputerRegistry.reset();
WirelessNetwork.resetNetworks();
Tracking.reset();
}
public static final ResourceLocation LOOT_TREASURE_DISK = new ResourceLocation( ComputerCraft.MOD_ID, "treasure_disk" );
private static final Set<ResourceLocation> TABLES = new HashSet<>( Arrays.asList(
LootTables.SIMPLE_DUNGEON,
LootTables.ABANDONED_MINESHAFT,
LootTables.STRONGHOLD_CORRIDOR,
LootTables.STRONGHOLD_CROSSING,
LootTables.STRONGHOLD_LIBRARY,
LootTables.DESERT_PYRAMID,
LootTables.JUNGLE_TEMPLE,
LootTables.IGLOO_CHEST,
LootTables.WOODLAND_MANSION,
LootTables.VILLAGE_CARTOGRAPHER
) );
@SubscribeEvent
public static void lootLoad( LootTableLoadEvent event )
{
ResourceLocation name = event.getName();
if( !name.getNamespace().equals( "minecraft" ) || !TABLES.contains( name ) ) return;
event.getTable().addPool( LootPool.lootPool()
.add( TableLootEntry.lootTableReference( LOOT_TREASURE_DISK ) )
.setRolls( ConstantRange.exactly( 1 ) )
.name( "computercraft_treasure" )
.build() );
}
}
}

View File

@ -160,8 +160,8 @@ public class TurtleBrain implements ITurtleAccess
overlay = nbt.contains( NBT_OVERLAY ) ? new ResourceLocation( nbt.getString( NBT_OVERLAY ) ) : null; overlay = nbt.contains( NBT_OVERLAY ) ? new ResourceLocation( nbt.getString( NBT_OVERLAY ) ) : null;
// Read upgrades // Read upgrades
setUpgrade( TurtleSide.LEFT, nbt.contains( NBT_LEFT_UPGRADE ) ? TurtleUpgrades.get( nbt.getString( NBT_LEFT_UPGRADE ) ) : null ); setUpgradeDirect( 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 ); setUpgradeDirect( TurtleSide.RIGHT, nbt.contains( NBT_RIGHT_UPGRADE ) ? TurtleUpgrades.get( nbt.getString( NBT_RIGHT_UPGRADE ) ) : null );
// NBT // NBT
upgradeNBTData.clear(); upgradeNBTData.clear();
@ -618,16 +618,30 @@ public class TurtleBrain implements ITurtleAccess
@Override @Override
public void setUpgrade( @Nonnull TurtleSide side, ITurtleUpgrade upgrade ) public void setUpgrade( @Nonnull TurtleSide side, ITurtleUpgrade upgrade )
{
if( !setUpgradeDirect( side, upgrade ) ) return;
// This is a separate function to avoid updating the block when reading the NBT. We don't need to do this as
// either the block is newly placed (and so won't have changed) or is being updated with /data, which calls
// updateBlock for us.
if( owner.getLevel() != null )
{
owner.updateBlock();
owner.updateInput();
}
}
private boolean setUpgradeDirect( @Nonnull TurtleSide side, ITurtleUpgrade upgrade )
{ {
// Remove old upgrade // Remove old upgrade
if( upgrades.containsKey( side ) ) if( upgrades.containsKey( side ) )
{ {
if( upgrades.get( side ) == upgrade ) return; if( upgrades.get( side ) == upgrade ) return false;
upgrades.remove( side ); upgrades.remove( side );
} }
else else
{ {
if( upgrade == null ) return; if( upgrade == null ) return false;
} }
upgradeNBTData.remove( side ); upgradeNBTData.remove( side );
@ -639,8 +653,9 @@ public class TurtleBrain implements ITurtleAccess
if( owner.getLevel() != null ) if( owner.getLevel() != null )
{ {
updatePeripherals( owner.createServerComputer() ); updatePeripherals( owner.createServerComputer() );
owner.updateBlock();
} }
return true;
} }
@Override @Override

View File

@ -62,7 +62,7 @@ public class TurtleDropCommand implements ITurtleCommand
IItemHandler inventory = InventoryUtil.getInventory( world, newPosition, side ); IItemHandler inventory = InventoryUtil.getInventory( world, newPosition, side );
// Fire the event, restoring the inventory and exiting if it is cancelled. // Fire the event, restoring the inventory and exiting if it is cancelled.
TurtlePlayer player = TurtlePlaceCommand.createPlayer( turtle, oldPosition, direction ); TurtlePlayer player = TurtlePlayer.getWithPosition( turtle, oldPosition, direction );
TurtleInventoryEvent.Drop event = new TurtleInventoryEvent.Drop( turtle, player, world, newPosition, inventory, stack ); TurtleInventoryEvent.Drop event = new TurtleInventoryEvent.Drop( turtle, player, world, newPosition, inventory, stack );
if( MinecraftForge.EVENT_BUS.post( event ) ) if( MinecraftForge.EVENT_BUS.post( event ) )
{ {

View File

@ -50,7 +50,7 @@ public class TurtleInspectCommand implements ITurtleCommand
Map<String, Object> table = BlockData.fill( new HashMap<>(), state ); Map<String, Object> table = BlockData.fill( new HashMap<>(), state );
// Fire the event, exiting if it is cancelled // Fire the event, exiting if it is cancelled
TurtlePlayer turtlePlayer = TurtlePlaceCommand.createPlayer( turtle, oldPosition, direction ); TurtlePlayer turtlePlayer = TurtlePlayer.getWithPosition( turtle, oldPosition, direction );
TurtleBlockEvent.Inspect event = new TurtleBlockEvent.Inspect( turtle, turtlePlayer, world, newPosition, state, table ); TurtleBlockEvent.Inspect event = new TurtleBlockEvent.Inspect( turtle, turtlePlayer, world, newPosition, state, table );
if( MinecraftForge.EVENT_BUS.post( event ) ) return TurtleCommandResult.failure( event.getFailureMessage() ); if( MinecraftForge.EVENT_BUS.post( event ) ) return TurtleCommandResult.failure( event.getFailureMessage() );

View File

@ -47,7 +47,7 @@ public class TurtleMoveCommand implements ITurtleCommand
BlockPos oldPosition = turtle.getPosition(); BlockPos oldPosition = turtle.getPosition();
BlockPos newPosition = oldPosition.relative( direction ); BlockPos newPosition = oldPosition.relative( direction );
TurtlePlayer turtlePlayer = TurtlePlaceCommand.createPlayer( turtle, oldPosition, direction ); TurtlePlayer turtlePlayer = TurtlePlayer.getWithPosition( turtle, oldPosition, direction );
TurtleCommandResult canEnterResult = canEnter( turtlePlayer, oldWorld, newPosition ); TurtleCommandResult canEnterResult = canEnter( turtlePlayer, oldWorld, newPosition );
if( !canEnterResult.isSuccess() ) if( !canEnterResult.isSuccess() )
{ {

View File

@ -12,7 +12,6 @@ import dan200.computercraft.api.turtle.TurtleAnimation;
import dan200.computercraft.api.turtle.TurtleCommandResult; import dan200.computercraft.api.turtle.TurtleCommandResult;
import dan200.computercraft.api.turtle.event.TurtleBlockEvent; import dan200.computercraft.api.turtle.event.TurtleBlockEvent;
import dan200.computercraft.shared.TurtlePermissions; import dan200.computercraft.shared.TurtlePermissions;
import dan200.computercraft.shared.util.DirectionUtil;
import dan200.computercraft.shared.util.DropConsumer; import dan200.computercraft.shared.util.DropConsumer;
import dan200.computercraft.shared.util.InventoryUtil; import dan200.computercraft.shared.util.InventoryUtil;
import dan200.computercraft.shared.util.WorldUtil; import dan200.computercraft.shared.util.WorldUtil;
@ -20,6 +19,7 @@ import net.minecraft.block.BlockState;
import net.minecraft.entity.Entity; import net.minecraft.entity.Entity;
import net.minecraft.entity.LivingEntity; import net.minecraft.entity.LivingEntity;
import net.minecraft.item.*; import net.minecraft.item.*;
import net.minecraft.network.play.client.CUseEntityPacket;
import net.minecraft.tileentity.SignTileEntity; import net.minecraft.tileentity.SignTileEntity;
import net.minecraft.tileentity.TileEntity; import net.minecraft.tileentity.TileEntity;
import net.minecraft.util.ActionResult; import net.minecraft.util.ActionResult;
@ -34,11 +34,13 @@ import net.minecraft.world.World;
import net.minecraftforge.common.ForgeHooks; import net.minecraftforge.common.ForgeHooks;
import net.minecraftforge.common.MinecraftForge; import net.minecraftforge.common.MinecraftForge;
import net.minecraftforge.event.entity.player.PlayerInteractEvent; import net.minecraftforge.event.entity.player.PlayerInteractEvent;
import net.minecraftforge.eventbus.api.Event; import net.minecraftforge.items.IItemHandler;
import net.minecraftforge.items.wrapper.InvWrapper;
import org.apache.commons.lang3.tuple.Pair; import org.apache.commons.lang3.tuple.Pair;
import javax.annotation.Nonnull; import javax.annotation.Nonnull;
import java.util.List;
import static net.minecraftforge.eventbus.api.Event.Result;
public class TurtlePlaceCommand implements ITurtleCommand public class TurtlePlaceCommand implements ITurtleCommand
{ {
@ -57,10 +59,7 @@ public class TurtlePlaceCommand implements ITurtleCommand
{ {
// Get thing to place // Get thing to place
ItemStack stack = turtle.getInventory().getItem( turtle.getSelectedSlot() ); ItemStack stack = turtle.getInventory().getItem( turtle.getSelectedSlot() );
if( stack.isEmpty() ) if( stack.isEmpty() ) return TurtleCommandResult.failure( "No items to place" );
{
return TurtleCommandResult.failure( "No items to place" );
}
// Remember old block // Remember old block
Direction direction = this.direction.toWorldDir( turtle ); Direction direction = this.direction.toWorldDir( turtle );
@ -68,144 +67,63 @@ public class TurtlePlaceCommand implements ITurtleCommand
// Create a fake player, and orient it appropriately // Create a fake player, and orient it appropriately
BlockPos playerPosition = turtle.getPosition().relative( direction ); BlockPos playerPosition = turtle.getPosition().relative( direction );
TurtlePlayer turtlePlayer = createPlayer( turtle, playerPosition, direction ); TurtlePlayer turtlePlayer = TurtlePlayer.getWithPosition( turtle, playerPosition, direction );
TurtleBlockEvent.Place place = new TurtleBlockEvent.Place( turtle, turtlePlayer, turtle.getWorld(), coordinates, stack ); TurtleBlockEvent.Place place = new TurtleBlockEvent.Place( turtle, turtlePlayer, turtle.getWorld(), coordinates, stack );
if( MinecraftForge.EVENT_BUS.post( place ) ) if( MinecraftForge.EVENT_BUS.post( place ) ) return TurtleCommandResult.failure( place.getFailureMessage() );
{
return TurtleCommandResult.failure( place.getFailureMessage() );
}
// Do the deploying // Do the deploying
String[] errorMessage = new String[1]; turtlePlayer.loadInventory( turtle );
ItemStack remainder = deploy( stack, turtle, turtlePlayer, direction, extraArguments, errorMessage ); ErrorMessage message = new ErrorMessage();
if( remainder != stack ) boolean result = deploy( stack, turtle, turtlePlayer, direction, extraArguments, message );
turtlePlayer.unloadInventory( turtle );
if( result )
{ {
// Put the remaining items back
turtle.getInventory().setItem( turtle.getSelectedSlot(), remainder );
turtle.getInventory().setChanged();
// Animate and return success // Animate and return success
turtle.playAnimation( TurtleAnimation.WAIT ); turtle.playAnimation( TurtleAnimation.WAIT );
return TurtleCommandResult.success(); return TurtleCommandResult.success();
} }
else else if( message.message != null )
{ {
if( errorMessage[0] != null ) return TurtleCommandResult.failure( message.message );
{
return TurtleCommandResult.failure( errorMessage[0] );
}
else if( stack.getItem() instanceof BlockItem )
{
return TurtleCommandResult.failure( "Cannot place block here" );
} }
else else
{ {
return TurtleCommandResult.failure( "Cannot place item here" ); return TurtleCommandResult.failure( stack.getItem() instanceof BlockItem ? "Cannot place block here" : "Cannot place item here" );
}
} }
} }
public static ItemStack deploy( @Nonnull ItemStack stack, ITurtleAccess turtle, Direction direction, Object[] extraArguments, String[] outErrorMessage ) public static boolean deployCopiedItem( @Nonnull ItemStack stack, ITurtleAccess turtle, Direction direction, Object[] extraArguments, ErrorMessage outErrorMessage )
{ {
// Create a fake player, and orient it appropriately // Create a fake player, and orient it appropriately
BlockPos playerPosition = turtle.getPosition().relative( direction ); BlockPos playerPosition = turtle.getPosition().relative( direction );
TurtlePlayer turtlePlayer = createPlayer( turtle, playerPosition, direction ); TurtlePlayer turtlePlayer = TurtlePlayer.getWithPosition( turtle, playerPosition, direction );
turtlePlayer.loadInventory( stack );
return deploy( stack, turtle, turtlePlayer, direction, extraArguments, outErrorMessage ); boolean result = deploy( stack, turtle, turtlePlayer, direction, extraArguments, outErrorMessage );
turtlePlayer.inventory.clearContent();
return result;
} }
public static ItemStack deploy( @Nonnull ItemStack stack, ITurtleAccess turtle, TurtlePlayer turtlePlayer, Direction direction, Object[] extraArguments, String[] outErrorMessage ) private static boolean deploy( @Nonnull ItemStack stack, ITurtleAccess turtle, TurtlePlayer turtlePlayer, Direction direction, Object[] extraArguments, ErrorMessage outErrorMessage )
{ {
// Deploy on an entity // Deploy on an entity
ItemStack remainder = deployOnEntity( stack, turtle, turtlePlayer, direction, extraArguments, outErrorMessage ); if( deployOnEntity( stack, turtle, turtlePlayer ) ) return true;
if( remainder != stack )
{
return remainder;
}
// Deploy on the block immediately in front
BlockPos position = turtle.getPosition(); BlockPos position = turtle.getPosition();
BlockPos newPosition = position.relative( direction ); BlockPos newPosition = position.relative( direction );
remainder = deployOnBlock( stack, turtle, turtlePlayer, newPosition, direction.getOpposite(), extraArguments, true, outErrorMessage );
if( remainder != stack )
{
return remainder;
}
// Try to deploy against a block. Tries the following options:
// Deploy on the block immediately in front
return deployOnBlock( stack, turtle, turtlePlayer, newPosition, direction.getOpposite(), extraArguments, true, outErrorMessage )
// Deploy on the block one block away // Deploy on the block one block away
remainder = deployOnBlock( stack, turtle, turtlePlayer, newPosition.relative( direction ), direction.getOpposite(), extraArguments, false, outErrorMessage ); || deployOnBlock( stack, turtle, turtlePlayer, newPosition.relative( direction ), direction.getOpposite(), extraArguments, false, outErrorMessage )
if( remainder != stack )
{
return remainder;
}
if( direction.getAxis() != Direction.Axis.Y )
{
// Deploy down on the block in front // Deploy down on the block in front
remainder = deployOnBlock( stack, turtle, turtlePlayer, newPosition.below(), Direction.UP, extraArguments, false, outErrorMessage ); || (direction.getAxis() != Direction.Axis.Y && deployOnBlock( stack, turtle, turtlePlayer, newPosition.below(), Direction.UP, extraArguments, false, outErrorMessage ))
if( remainder != stack )
{
return remainder;
}
}
// Deploy back onto the turtle // Deploy back onto the turtle
remainder = deployOnBlock( stack, turtle, turtlePlayer, position, direction, extraArguments, false, outErrorMessage ); || deployOnBlock( stack, turtle, turtlePlayer, position, direction, extraArguments, false, outErrorMessage );
if( remainder != stack )
{
return remainder;
} }
// If nothing worked, return the original stack unchanged private static boolean deployOnEntity( @Nonnull ItemStack stack, final ITurtleAccess turtle, TurtlePlayer turtlePlayer )
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 )
{
double posX = position.getX() + 0.5;
double posY = position.getY() + 0.5;
double posZ = position.getZ() + 0.5;
// Stop intersection with the turtle itself
if( turtle.getPosition().equals( position ) )
{
posX += 0.48 * direction.getStepX();
posY += 0.48 * direction.getStepY();
posZ += 0.48 * direction.getStepZ();
}
if( direction.getAxis() != Direction.Axis.Y )
{
turtlePlayer.yRot = direction.toYRot();
turtlePlayer.xRot = 0.0f;
}
else
{
turtlePlayer.yRot = turtle.getDirection().toYRot();
turtlePlayer.xRot = DirectionUtil.toPitchAngle( direction );
}
turtlePlayer.setPosRaw( posX, posY, posZ );
turtlePlayer.xo = posX;
turtlePlayer.yo = posY;
turtlePlayer.zo = posZ;
turtlePlayer.xRotO = turtlePlayer.xRot;
turtlePlayer.yRotO = turtlePlayer.yRot;
turtlePlayer.yHeadRot = turtlePlayer.yRot;
turtlePlayer.yHeadRotO = turtlePlayer.yHeadRot;
}
@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 // See if there is an entity present
final World world = turtle.getWorld(); final World world = turtle.getWorld();
@ -213,81 +131,57 @@ public class TurtlePlaceCommand implements ITurtleCommand
Vector3d turtlePos = turtlePlayer.position(); Vector3d turtlePos = turtlePlayer.position();
Vector3d rayDir = turtlePlayer.getViewVector( 1.0f ); Vector3d rayDir = turtlePlayer.getViewVector( 1.0f );
Pair<Entity, Vector3d> hit = WorldUtil.rayTraceEntities( world, turtlePos, rayDir, 1.5 ); Pair<Entity, Vector3d> hit = WorldUtil.rayTraceEntities( world, turtlePos, rayDir, 1.5 );
if( hit == null ) if( hit == null ) return false;
{
return stack;
}
// Load up the turtle's inventory
ItemStack stackCopy = stack.copy();
turtlePlayer.loadInventory( stackCopy );
// Start claiming entity drops // Start claiming entity drops
Entity hitEntity = hit.getKey(); Entity hitEntity = hit.getKey();
Vector3d hitPos = hit.getValue(); Vector3d hitPos = hit.getValue();
DropConsumer.set(
hitEntity,
drop -> InventoryUtil.storeItems( drop, turtle.getItemHandler(), turtle.getSelectedSlot() )
);
// Place on the entity IItemHandler itemHandler = new InvWrapper( turtlePlayer.inventory );
boolean placed = false; DropConsumer.set( hitEntity, drop -> InventoryUtil.storeItems( drop, itemHandler, 1 ) );
ActionResultType cancelResult = ForgeHooks.onInteractEntityAt( turtlePlayer, hitEntity, hitPos, Hand.MAIN_HAND );
if( cancelResult == null ) boolean placed = doDeployOnEntity( stack, turtlePlayer, hitEntity, hitPos );
{
cancelResult = hitEntity.interactAt( turtlePlayer, hitPos, Hand.MAIN_HAND ); DropConsumer.clearAndDrop( world, position, turtle.getDirection().getOpposite() );
return placed;
} }
if( cancelResult.consumesAction() ) /**
* Place a block onto an entity. For instance, feeding cows.
*
* @param stack The stack we're placing.
* @param turtlePlayer The player of the turtle we're placing.
* @param hitEntity The entity we're interacting with.
* @param hitPos The position our ray trace hit the entity.
* @return If this item was deployed.
* @see net.minecraft.network.play.ServerPlayNetHandler#handleInteract(CUseEntityPacket)
* @see net.minecraft.entity.player.PlayerEntity#interactOn(Entity, Hand)
*/
private static boolean doDeployOnEntity( @Nonnull ItemStack stack, TurtlePlayer turtlePlayer, @Nonnull Entity hitEntity, @Nonnull Vector3d hitPos )
{ {
placed = true; // Placing "onto" a block follows two flows. First we try to interactAt. If that doesn't succeed, then we try to
} // call the normal interact path. Cancelling an interactAt *does not* cancel a normal interact path.
else
ActionResultType interactAt = ForgeHooks.onInteractEntityAt( turtlePlayer, hitEntity, hitPos, Hand.MAIN_HAND );
if( interactAt == null ) interactAt = hitEntity.interactAt( turtlePlayer, hitPos, Hand.MAIN_HAND );
if( interactAt.consumesAction() ) return true;
ActionResultType interact = ForgeHooks.onInteractEntity( turtlePlayer, hitEntity, Hand.MAIN_HAND );
if( interact != null ) return interact.consumesAction();
if( hitEntity.interact( turtlePlayer, Hand.MAIN_HAND ).consumesAction() ) return true;
if( hitEntity instanceof LivingEntity )
{ {
// See EntityPlayer.interactOn return stack.interactLivingEntity( turtlePlayer, (LivingEntity) hitEntity, Hand.MAIN_HAND ).consumesAction();
cancelResult = ForgeHooks.onInteractEntity( turtlePlayer, hitEntity, Hand.MAIN_HAND );
if( cancelResult != null && cancelResult.consumesAction() )
{
placed = true;
}
else if( cancelResult == null )
{
if( hitEntity.interact( turtlePlayer, Hand.MAIN_HAND ) == ActionResultType.CONSUME )
{
placed = true;
}
else if( hitEntity instanceof LivingEntity )
{
placed = stackCopy.interactLivingEntity( turtlePlayer, (LivingEntity) hitEntity, Hand.MAIN_HAND ).consumesAction();
if( placed ) turtlePlayer.loadInventory( stackCopy );
}
}
} }
// Stop claiming drops return false;
List<ItemStack> 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 private static boolean canDeployOnBlock(
ItemStack remainder = turtlePlayer.unloadInventory( turtle ); @Nonnull BlockItemUseContext context, ITurtleAccess turtle, TurtlePlayer player, BlockPos position,
if( !placed && ItemStack.matches( stack, remainder ) ) Direction side, boolean allowReplaceable, ErrorMessage outErrorMessage
{ )
return stack;
}
else if( !remainder.isEmpty() )
{
return remainder;
}
else
{
return ItemStack.EMPTY;
}
}
private static boolean canDeployOnBlock( @Nonnull BlockItemUseContext context, ITurtleAccess turtle, TurtlePlayer player, BlockPos position, Direction side, boolean allowReplaceable, String[] outErrorMessage )
{ {
World world = turtle.getWorld(); World world = turtle.getWorld();
if( !World.isInWorldBounds( position ) || world.isEmptyBlock( position ) || if( !World.isInWorldBounds( position ) || world.isEmptyBlock( position ) ||
@ -309,7 +203,7 @@ public class TurtlePlaceCommand implements ITurtleCommand
: TurtlePermissions.isBlockEditable( world, position.relative( side ), player ); : TurtlePermissions.isBlockEditable( world, position.relative( side ), player );
if( !editable ) if( !editable )
{ {
if( outErrorMessage != null ) outErrorMessage[0] = "Cannot place in protected area"; if( outErrorMessage != null ) outErrorMessage.message = "Cannot place in protected area";
return false; return false;
} }
} }
@ -317,79 +211,37 @@ public class TurtlePlaceCommand implements ITurtleCommand
return true; return true;
} }
@Nonnull private static boolean deployOnBlock(
private static ItemStack deployOnBlock( @Nonnull ItemStack stack, ITurtleAccess turtle, TurtlePlayer turtlePlayer, BlockPos position, Direction side, Object[] extraArguments, boolean allowReplace, String[] outErrorMessage ) @Nonnull ItemStack stack, ITurtleAccess turtle, TurtlePlayer turtlePlayer, BlockPos position, Direction side,
Object[] extraArguments, boolean allowReplace, ErrorMessage outErrorMessage
)
{ {
// Re-orient the fake player // Re-orient the fake player
Direction playerDir = side.getOpposite(); Direction playerDir = side.getOpposite();
BlockPos playerPosition = position.relative( side ); BlockPos playerPosition = position.relative( side );
orientPlayer( turtle, turtlePlayer, playerPosition, playerDir ); turtlePlayer.setPosition( turtle, playerPosition, playerDir );
ItemStack stackCopy = stack.copy();
turtlePlayer.loadInventory( stackCopy );
// Calculate where the turtle would hit the block // Calculate where the turtle would hit the block
float hitX = 0.5f + side.getStepX() * 0.5f; float hitX = 0.5f + side.getStepX() * 0.5f;
float hitY = 0.5f + side.getStepY() * 0.5f; float hitY = 0.5f + side.getStepY() * 0.5f;
float hitZ = 0.5f + side.getStepZ() * 0.5f; float hitZ = 0.5f + side.getStepZ() * 0.5f;
if( Math.abs( hitY - 0.5f ) < 0.01f ) if( Math.abs( hitY - 0.5f ) < 0.01f ) hitY = 0.45f;
{
hitY = 0.45f;
}
// Check if there's something suitable to place onto // Check if there's something suitable to place onto
BlockRayTraceResult hit = new BlockRayTraceResult( new Vector3d( hitX, hitY, hitZ ), side, position, false ); BlockRayTraceResult hit = new BlockRayTraceResult( new Vector3d( hitX, hitY, hitZ ), side, position, false );
ItemUseContext context = new ItemUseContext( turtlePlayer, Hand.MAIN_HAND, hit ); ItemUseContext context = new ItemUseContext( turtlePlayer, Hand.MAIN_HAND, hit );
if( !canDeployOnBlock( new BlockItemUseContext( context ), turtle, turtlePlayer, position, side, allowReplace, outErrorMessage ) ) if( !canDeployOnBlock( new BlockItemUseContext( context ), turtle, turtlePlayer, position, side, allowReplace, outErrorMessage ) )
{ {
return stack; return false;
} }
// Load up the turtle's inventory
Item item = stack.getItem(); Item item = stack.getItem();
// Do the deploying (put everything in the players inventory)
boolean placed = false;
TileEntity existingTile = turtle.getWorld().getBlockEntity( position ); TileEntity existingTile = turtle.getWorld().getBlockEntity( position );
// See PlayerInteractionManager.processRightClickBlock boolean placed = doDeployOnBlock( stack, turtlePlayer, position, context, hit ).consumesAction();
PlayerInteractEvent.RightClickBlock event = ForgeHooks.onRightClickBlock( turtlePlayer, Hand.MAIN_HAND, position, hit );
if( !event.isCanceled() )
{
if( item.onItemUseFirst( stack, context ).consumesAction() )
{
placed = true;
turtlePlayer.loadInventory( stackCopy );
}
else if( event.getUseItem() != Event.Result.DENY && stackCopy.useOn( context ).consumesAction() )
{
placed = true;
turtlePlayer.loadInventory( stackCopy );
}
}
if( !placed && (item instanceof BucketItem || item instanceof BoatItem || item instanceof LilyPadItem || item instanceof GlassBottleItem) )
{
ActionResultType actionResult = ForgeHooks.onItemRightClick( turtlePlayer, Hand.MAIN_HAND );
if( actionResult != null && actionResult.consumesAction() )
{
placed = true;
}
else if( actionResult == null )
{
ActionResult<ItemStack> result = stackCopy.use( turtle.getWorld(), turtlePlayer, Hand.MAIN_HAND );
if( result.getResult().consumesAction() && !ItemStack.matches( stack, result.getObject() ) )
{
placed = true;
turtlePlayer.loadInventory( result.getObject() );
}
}
}
// Set text on signs // Set text on signs
if( placed && item instanceof SignItem ) if( placed && item instanceof SignItem && extraArguments != null && extraArguments.length >= 1 && extraArguments[0] instanceof String )
{
if( extraArguments != null && extraArguments.length >= 1 && extraArguments[0] instanceof String )
{ {
World world = turtle.getWorld(); World world = turtle.getWorld();
TileEntity tile = world.getBlockEntity( position ); TileEntity tile = world.getBlockEntity( position );
@ -397,24 +249,74 @@ public class TurtlePlaceCommand implements ITurtleCommand
{ {
tile = world.getBlockEntity( position.relative( side ) ); tile = world.getBlockEntity( position.relative( side ) );
} }
if( tile instanceof SignTileEntity )
if( tile instanceof SignTileEntity ) setSignText( world, tile, (String) extraArguments[0] );
}
return placed;
}
/**
* Attempt to place an item into the world. Returns true/false if an item was placed.
*
* @param stack The stack the player is using.
* @param turtlePlayer The player which represents the turtle
* @param position The block we're deploying against's position.
* @param context The context of this place action.
* @param hit Where the block we're placing against was clicked.
* @return If this item was deployed.
* @see net.minecraft.server.management.PlayerInteractionManager#useItemOn For the original implementation.
*/
private static ActionResultType doDeployOnBlock(
@Nonnull ItemStack stack, TurtlePlayer turtlePlayer, BlockPos position, ItemUseContext context, BlockRayTraceResult hit
)
{
PlayerInteractEvent.RightClickBlock event = ForgeHooks.onRightClickBlock( turtlePlayer, Hand.MAIN_HAND, position, hit );
if( event.isCanceled() ) return event.getCancellationResult();
if( event.getUseItem() != Result.DENY )
{
ActionResultType result = stack.onItemUseFirst( context );
if( result != ActionResultType.PASS ) return result;
}
if( event.getUseItem() != Result.DENY )
{
ActionResultType result = stack.useOn( context );
if( result != ActionResultType.PASS ) return result;
}
Item item = stack.getItem();
if( item instanceof BucketItem || item instanceof BoatItem || item instanceof LilyPadItem || item instanceof GlassBottleItem )
{
ActionResultType actionResult = ForgeHooks.onItemRightClick( turtlePlayer, Hand.MAIN_HAND );
if( actionResult != null && actionResult != ActionResultType.PASS ) return actionResult;
ActionResult<ItemStack> result = stack.use( context.getLevel(), turtlePlayer, Hand.MAIN_HAND );
if( result.getResult().consumesAction() && !ItemStack.matches( stack, result.getObject() ) )
{
turtlePlayer.setItemInHand( Hand.MAIN_HAND, result.getObject() );
return result.getResult();
}
}
return ActionResultType.PASS;
}
private static void setSignText( World world, TileEntity tile, String message )
{ {
SignTileEntity signTile = (SignTileEntity) tile; SignTileEntity signTile = (SignTileEntity) tile;
String s = (String) extraArguments[0]; String[] split = message.split( "\n" );
String[] split = s.split( "\n" );
int firstLine = split.length <= 2 ? 1 : 0; int firstLine = split.length <= 2 ? 1 : 0;
for( int i = 0; i < 4; i++ ) for( int i = 0; i < 4; i++ )
{ {
if( i >= firstLine && i < firstLine + split.length ) if( i >= firstLine && i < firstLine + split.length )
{ {
if( split[i - firstLine].length() > 15 ) String line = split[i - firstLine];
{ signTile.setMessage( i, line.length() > 15
signTile.setMessage( i, new StringTextComponent( split[i - firstLine].substring( 0, 15 ) ) ); ? new StringTextComponent( line.substring( 0, 15 ) )
} : new StringTextComponent( line )
else );
{
signTile.setMessage( i, new StringTextComponent( split[i - firstLine] ) );
}
} }
else else
{ {
@ -424,22 +326,9 @@ public class TurtlePlaceCommand implements ITurtleCommand
signTile.setChanged(); signTile.setChanged();
world.sendBlockUpdated( tile.getBlockPos(), tile.getBlockState(), tile.getBlockState(), 3 ); world.sendBlockUpdated( tile.getBlockPos(), tile.getBlockState(), tile.getBlockState(), 3 );
} }
}
}
// Put everything we collected into the turtles inventory, then return private static class ErrorMessage
ItemStack remainder = turtlePlayer.unloadInventory( turtle );
if( !placed && ItemStack.matches( stack, remainder ) )
{ {
return stack; String message;
}
else if( !remainder.isEmpty() )
{
return remainder;
}
else
{
return ItemStack.EMPTY;
}
} }
} }

View File

@ -9,6 +9,7 @@ import com.mojang.authlib.GameProfile;
import dan200.computercraft.ComputerCraft; import dan200.computercraft.ComputerCraft;
import dan200.computercraft.api.turtle.ITurtleAccess; import dan200.computercraft.api.turtle.ITurtleAccess;
import dan200.computercraft.shared.Registry; import dan200.computercraft.shared.Registry;
import dan200.computercraft.shared.util.DirectionUtil;
import dan200.computercraft.shared.util.FakeNetHandler; import dan200.computercraft.shared.util.FakeNetHandler;
import dan200.computercraft.shared.util.InventoryUtil; import dan200.computercraft.shared.util.InventoryUtil;
import dan200.computercraft.shared.util.WorldUtil; import dan200.computercraft.shared.util.WorldUtil;
@ -73,23 +74,6 @@ public final class TurtlePlayer extends FakePlayer
return profile != null && profile.isComplete() ? profile : DEFAULT_PROFILE; return profile != null && profile.isComplete() ? profile : DEFAULT_PROFILE;
} }
private void setState( ITurtleAccess turtle )
{
if( containerMenu != inventoryMenu )
{
ComputerCraft.log.warn( "Turtle has open container ({})", containerMenu );
doCloseContainer();
}
BlockPos position = turtle.getPosition();
setPosRaw( position.getX() + 0.5, position.getY() + 0.5, position.getZ() + 0.5 );
yRot = turtle.getDirection().toYRot();
xRot = 0.0f;
inventory.clearContent();
}
public static TurtlePlayer get( ITurtleAccess access ) public static TurtlePlayer get( ITurtleAccess access )
{ {
if( !(access instanceof TurtleBrain) ) return create( access ); if( !(access instanceof TurtleBrain) ) return create( access );
@ -109,37 +93,114 @@ public final class TurtlePlayer extends FakePlayer
return player; return player;
} }
public void loadInventory( @Nonnull ItemStack currentStack ) public static TurtlePlayer getWithPosition( ITurtleAccess turtle, BlockPos position, Direction direction )
{ {
// Load up the fake inventory TurtlePlayer turtlePlayer = get( turtle );
inventory.selected = 0; turtlePlayer.setPosition( turtle, position, direction );
inventory.setItem( 0, currentStack ); return turtlePlayer;
} }
public ItemStack unloadInventory( ITurtleAccess turtle ) private void setState( ITurtleAccess turtle )
{ {
// Get the item we placed with if( containerMenu != inventoryMenu )
ItemStack results = inventory.getItem( 0 ); {
inventory.setItem( 0, ItemStack.EMPTY ); ComputerCraft.log.warn( "Turtle has open container ({})", containerMenu );
doCloseContainer();
}
BlockPos position = turtle.getPosition();
setPosRaw( position.getX() + 0.5, position.getY() + 0.5, position.getZ() + 0.5 );
yRot = turtle.getDirection().toYRot();
xRot = 0.0f;
inventory.clearContent();
}
public void setPosition( ITurtleAccess turtle, BlockPos position, Direction direction )
{
double posX = position.getX() + 0.5;
double posY = position.getY() + 0.5;
double posZ = position.getZ() + 0.5;
// Stop intersection with the turtle itself
if( turtle.getPosition().equals( position ) )
{
posX += 0.48 * direction.getStepX();
posY += 0.48 * direction.getStepY();
posZ += 0.48 * direction.getStepZ();
}
if( direction.getAxis() != Direction.Axis.Y )
{
yRot = direction.toYRot();
xRot = 0.0f;
}
else
{
yRot = turtle.getDirection().toYRot();
xRot = DirectionUtil.toPitchAngle( direction );
}
setPosRaw( posX, posY, posZ );
xo = posX;
yo = posY;
zo = posZ;
xRotO = xRot;
yRotO = yRot;
yHeadRot = yRot;
yHeadRotO = yHeadRot;
}
public void loadInventory( @Nonnull ItemStack stack )
{
inventory.clearContent();
inventory.selected = 0;
inventory.setItem( 0, stack );
}
public void loadInventory( @Nonnull ITurtleAccess turtle )
{
inventory.clearContent();
int currentSlot = turtle.getSelectedSlot();
int slots = turtle.getItemHandler().getSlots();
// Load up the fake inventory
inventory.selected = 0;
for( int i = 0; i < slots; i++ )
{
inventory.setItem( i, turtle.getItemHandler().getStackInSlot( (currentSlot + i) % slots ) );
}
}
public void unloadInventory( ITurtleAccess turtle )
{
int currentSlot = turtle.getSelectedSlot();
int slots = turtle.getItemHandler().getSlots();
// Load up the fake inventory
inventory.selected = 0;
for( int i = 0; i < slots; i++ )
{
turtle.getItemHandler().setStackInSlot( (currentSlot + i) % slots, inventory.getItem( i ) );
}
// Store (or drop) anything else we found // Store (or drop) anything else we found
BlockPos dropPosition = turtle.getPosition(); BlockPos dropPosition = turtle.getPosition();
Direction dropDirection = turtle.getDirection().getOpposite(); Direction dropDirection = turtle.getDirection().getOpposite();
for( int i = 0; i < inventory.getContainerSize(); i++ ) int totalSize = inventory.getContainerSize();
for( int i = slots; i < totalSize; i++ )
{ {
ItemStack stack = inventory.getItem( i ); ItemStack remainder = InventoryUtil.storeItems( inventory.getItem( i ), turtle.getItemHandler(), turtle.getSelectedSlot() );
if( !stack.isEmpty() )
{
ItemStack remainder = InventoryUtil.storeItems( stack, turtle.getItemHandler(), turtle.getSelectedSlot() );
if( !remainder.isEmpty() ) if( !remainder.isEmpty() )
{ {
WorldUtil.dropItemStack( remainder, turtle.getWorld(), dropPosition, dropDirection ); WorldUtil.dropItemStack( remainder, turtle.getWorld(), dropPosition, dropDirection );
} }
inventory.setItem( i, ItemStack.EMPTY );
}
} }
inventory.setChanged(); inventory.setChanged();
return results;
} }
@Nonnull @Nonnull

View File

@ -58,7 +58,7 @@ public class TurtleSuckCommand implements ITurtleCommand
IItemHandler inventory = InventoryUtil.getInventory( world, blockPosition, side ); IItemHandler inventory = InventoryUtil.getInventory( world, blockPosition, side );
// Fire the event, exiting if it is cancelled. // Fire the event, exiting if it is cancelled.
TurtlePlayer player = TurtlePlaceCommand.createPlayer( turtle, turtlePosition, direction ); TurtlePlayer player = TurtlePlayer.getWithPosition( turtle, turtlePosition, direction );
TurtleInventoryEvent.Suck event = new TurtleInventoryEvent.Suck( turtle, player, world, blockPosition, inventory ); TurtleInventoryEvent.Suck event = new TurtleInventoryEvent.Suck( turtle, player, world, blockPosition, inventory );
if( MinecraftForge.EVENT_BUS.post( event ) ) if( MinecraftForge.EVENT_BUS.post( event ) )
{ {

View File

@ -13,14 +13,21 @@ import dan200.computercraft.shared.common.IColouredItem;
import dan200.computercraft.shared.computer.core.ComputerFamily; import dan200.computercraft.shared.computer.core.ComputerFamily;
import dan200.computercraft.shared.computer.items.ItemComputerBase; import dan200.computercraft.shared.computer.items.ItemComputerBase;
import dan200.computercraft.shared.turtle.blocks.BlockTurtle; import dan200.computercraft.shared.turtle.blocks.BlockTurtle;
import net.minecraft.block.BlockState;
import net.minecraft.block.Blocks;
import net.minecraft.block.CauldronBlock;
import net.minecraft.item.ItemGroup; import net.minecraft.item.ItemGroup;
import net.minecraft.item.ItemStack; import net.minecraft.item.ItemStack;
import net.minecraft.item.ItemUseContext;
import net.minecraft.nbt.CompoundNBT; import net.minecraft.nbt.CompoundNBT;
import net.minecraft.util.ActionResultType;
import net.minecraft.util.NonNullList; import net.minecraft.util.NonNullList;
import net.minecraft.util.ResourceLocation; import net.minecraft.util.ResourceLocation;
import net.minecraft.util.math.BlockPos;
import net.minecraft.util.text.ITextComponent; import net.minecraft.util.text.ITextComponent;
import net.minecraft.util.text.StringTextComponent; import net.minecraft.util.text.StringTextComponent;
import net.minecraft.util.text.TranslationTextComponent; import net.minecraft.util.text.TranslationTextComponent;
import net.minecraft.world.World;
import javax.annotation.Nonnull; import javax.annotation.Nonnull;
import javax.annotation.Nullable; import javax.annotation.Nullable;
@ -161,4 +168,27 @@ public class ItemTurtle extends ItemComputerBase implements ITurtleItem
CompoundNBT tag = stack.getTag(); CompoundNBT tag = stack.getTag();
return tag != null && tag.contains( NBT_FUEL ) ? tag.getInt( NBT_FUEL ) : 0; return tag != null && tag.contains( NBT_FUEL ) ? tag.getInt( NBT_FUEL ) : 0;
} }
@Override
public ActionResultType onItemUseFirst( ItemStack stack, ItemUseContext context )
{
if( context.isSecondaryUseActive() || getColour( stack ) == -1 ) return ActionResultType.PASS;
World level = context.getLevel();
BlockPos pos = context.getClickedPos();
BlockState blockState = level.getBlockState( pos );
if( blockState.getBlock() != Blocks.CAULDRON ) return ActionResultType.PASS;
int waterLevel = blockState.getValue( CauldronBlock.LEVEL );
if( waterLevel <= 0 ) return ActionResultType.PASS;
if( !level.isClientSide )
{
((CauldronBlock) blockState.getBlock()).setWaterLevel( level, pos, blockState, waterLevel - 1 );
IColouredItem.setColourBasic( stack, -1 );
}
return ActionResultType.SUCCESS;
}
} }

View File

@ -59,9 +59,7 @@ public class TurtleHoe extends TurtleTool
{ {
if( verb == TurtleVerb.DIG ) if( verb == TurtleVerb.DIG )
{ {
ItemStack hoe = item.copy(); if( TurtlePlaceCommand.deployCopiedItem( item.copy(), turtle, direction, null, null ) )
ItemStack remainder = TurtlePlaceCommand.deploy( hoe, turtle, direction, null, null );
if( remainder != hoe )
{ {
return TurtleCommandResult.success(); return TurtleCommandResult.success();
} }

View File

@ -63,9 +63,7 @@ public class TurtleShovel extends TurtleTool
{ {
if( verb == TurtleVerb.DIG ) if( verb == TurtleVerb.DIG )
{ {
ItemStack shovel = item.copy(); if( TurtlePlaceCommand.deployCopiedItem( item.copy(), turtle, direction, null, null ) )
ItemStack remainder = TurtlePlaceCommand.deploy( shovel, turtle, direction, null, null );
if( remainder != shovel )
{ {
return TurtleCommandResult.success(); return TurtleCommandResult.success();
} }

View File

@ -12,7 +12,6 @@ import dan200.computercraft.api.turtle.event.TurtleAttackEvent;
import dan200.computercraft.api.turtle.event.TurtleBlockEvent; import dan200.computercraft.api.turtle.event.TurtleBlockEvent;
import dan200.computercraft.shared.TurtlePermissions; import dan200.computercraft.shared.TurtlePermissions;
import dan200.computercraft.shared.turtle.core.TurtleBrain; import dan200.computercraft.shared.turtle.core.TurtleBrain;
import dan200.computercraft.shared.turtle.core.TurtlePlaceCommand;
import dan200.computercraft.shared.turtle.core.TurtlePlayer; import dan200.computercraft.shared.turtle.core.TurtlePlayer;
import dan200.computercraft.shared.util.DropConsumer; import dan200.computercraft.shared.util.DropConsumer;
import dan200.computercraft.shared.util.InventoryUtil; import dan200.computercraft.shared.util.InventoryUtil;
@ -45,7 +44,6 @@ import net.minecraftforge.event.world.BlockEvent;
import org.apache.commons.lang3.tuple.Pair; import org.apache.commons.lang3.tuple.Pair;
import javax.annotation.Nonnull; import javax.annotation.Nonnull;
import java.util.List;
import java.util.function.Function; import java.util.function.Function;
public class TurtleTool extends AbstractTurtleUpgrade public class TurtleTool extends AbstractTurtleUpgrade
@ -140,7 +138,7 @@ public class TurtleTool extends AbstractTurtleUpgrade
TileEntity turtleTile = turtle instanceof TurtleBrain ? ((TurtleBrain) turtle).getOwner() : world.getBlockEntity( position ); TileEntity turtleTile = turtle instanceof TurtleBrain ? ((TurtleBrain) turtle).getOwner() : world.getBlockEntity( position );
if( turtleTile == null ) return TurtleCommandResult.failure( "Turtle has vanished from existence." ); if( turtleTile == null ) return TurtleCommandResult.failure( "Turtle has vanished from existence." );
final TurtlePlayer turtlePlayer = TurtlePlaceCommand.createPlayer( turtle, position, direction ); final TurtlePlayer turtlePlayer = TurtlePlayer.getWithPosition( turtle, position, direction );
// See if there is an entity present // See if there is an entity present
Vector3d turtlePos = turtlePlayer.position(); Vector3d turtlePos = turtlePlayer.position();
@ -204,7 +202,7 @@ public class TurtleTool extends AbstractTurtleUpgrade
// Put everything we collected into the turtles inventory, then return // Put everything we collected into the turtles inventory, then return
if( attacked ) if( attacked )
{ {
turtlePlayer.unloadInventory( turtle ); turtlePlayer.inventory.clearContent();
return TurtleCommandResult.success(); return TurtleCommandResult.success();
} }
} }
@ -229,7 +227,7 @@ public class TurtleTool extends AbstractTurtleUpgrade
BlockState state = world.getBlockState( blockPosition ); BlockState state = world.getBlockState( blockPosition );
FluidState fluidState = world.getFluidState( blockPosition ); FluidState fluidState = world.getFluidState( blockPosition );
TurtlePlayer turtlePlayer = TurtlePlaceCommand.createPlayer( turtle, turtlePosition, direction ); TurtlePlayer turtlePlayer = TurtlePlayer.getWithPosition( turtle, turtlePosition, direction );
turtlePlayer.loadInventory( item.copy() ); turtlePlayer.loadInventory( item.copy() );
if( ComputerCraft.turtlesObeyBlockProtection ) if( ComputerCraft.turtlesObeyBlockProtection )
@ -293,10 +291,6 @@ public class TurtleTool extends AbstractTurtleUpgrade
private static void stopConsuming( TileEntity tile, ITurtleAccess turtle ) private static void stopConsuming( TileEntity tile, ITurtleAccess turtle )
{ {
Direction direction = tile.isRemoved() ? null : turtle.getDirection().getOpposite(); Direction direction = tile.isRemoved() ? null : turtle.getDirection().getOpposite();
List<ItemStack> extra = DropConsumer.clear(); DropConsumer.clearAndDrop( turtle.getWorld(), turtle.getPosition(), direction );
for( ItemStack remainder : extra )
{
WorldUtil.dropItemStack( remainder, turtle.getWorld(), turtle.getPosition(), direction );
}
} }
} }

View File

@ -9,6 +9,7 @@ import dan200.computercraft.ComputerCraft;
import net.minecraft.entity.Entity; import net.minecraft.entity.Entity;
import net.minecraft.entity.item.ItemEntity; import net.minecraft.entity.item.ItemEntity;
import net.minecraft.item.ItemStack; import net.minecraft.item.ItemStack;
import net.minecraft.util.Direction;
import net.minecraft.util.math.AxisAlignedBB; import net.minecraft.util.math.AxisAlignedBB;
import net.minecraft.util.math.BlockPos; import net.minecraft.util.math.BlockPos;
import net.minecraft.world.World; import net.minecraft.world.World;
@ -66,6 +67,12 @@ public final class DropConsumer
return remainingStacks; return remainingStacks;
} }
public static void clearAndDrop( World world, BlockPos pos, Direction direction )
{
List<ItemStack> remainingDrops = clear();
for( ItemStack remaining : remainingDrops ) WorldUtil.dropItemStack( remaining, world, pos, direction );
}
private static void handleDrops( ItemStack stack ) private static void handleDrops( ItemStack stack )
{ {
ItemStack remaining = dropConsumer.apply( stack ); ItemStack remaining = dropConsumer.apply( stack );

View File

@ -34,8 +34,8 @@
"upgrade.minecraft.diamond_axe.adjective": "Holzfällen", "upgrade.minecraft.diamond_axe.adjective": "Holzfällen",
"upgrade.minecraft.diamond_hoe.adjective": "Ackerbau", "upgrade.minecraft.diamond_hoe.adjective": "Ackerbau",
"upgrade.minecraft.crafting_table.adjective": "Handwerk", "upgrade.minecraft.crafting_table.adjective": "Handwerk",
"upgrade.computercraft.wireless_modem_normal.adjective": "Ender", "upgrade.computercraft.wireless_modem_normal.adjective": "Kabellos",
"upgrade.computercraft.wireless_modem_advanced.adjective": "Kabellos", "upgrade.computercraft.wireless_modem_advanced.adjective": "Ender",
"upgrade.computercraft.speaker.adjective": "Laut", "upgrade.computercraft.speaker.adjective": "Laut",
"chat.computercraft.wired_modem.peripheral_connected": "Peripheriegerät \"%s\" mit dem Netzwerk verbunden", "chat.computercraft.wired_modem.peripheral_connected": "Peripheriegerät \"%s\" mit dem Netzwerk verbunden",
"chat.computercraft.wired_modem.peripheral_disconnected": "Peripheriegerät \"%s\" vom Netzwerk getrennt", "chat.computercraft.wired_modem.peripheral_disconnected": "Peripheriegerät \"%s\" vom Netzwerk getrennt",

View File

@ -48,6 +48,7 @@
"commands.computercraft.dump.synopsis": "Display the status of computers.", "commands.computercraft.dump.synopsis": "Display the status of computers.",
"commands.computercraft.dump.desc": "Display the status of all computers or specific information about one computer. You can specify the computer's instance id (e.g. 123), computer id (e.g #123) or label (e.g. \"@My Computer\").", "commands.computercraft.dump.desc": "Display the status of all computers or specific information about one computer. You can specify the computer's instance id (e.g. 123), computer id (e.g #123) or label (e.g. \"@My Computer\").",
"commands.computercraft.dump.action": "View more info about this computer", "commands.computercraft.dump.action": "View more info about this computer",
"commands.computercraft.dump.open_path": "View this computer's files",
"commands.computercraft.shutdown.synopsis": "Shutdown computers remotely.", "commands.computercraft.shutdown.synopsis": "Shutdown computers remotely.",
"commands.computercraft.shutdown.desc": "Shutdown the listed computers or all if none are specified. You can specify the computer's instance id (e.g. 123), computer id (e.g #123) or label (e.g. \"@My Computer\").", "commands.computercraft.shutdown.desc": "Shutdown the listed computers or all if none are specified. You can specify the computer's instance id (e.g. 123), computer id (e.g #123) or label (e.g. \"@My Computer\").",
"commands.computercraft.shutdown.done": "Shutdown %s/%s computers", "commands.computercraft.shutdown.done": "Shutdown %s/%s computers",

View File

@ -17,7 +17,7 @@
"block.computercraft.turtle_normal.upgraded_twice": "Tortue %s et %s", "block.computercraft.turtle_normal.upgraded_twice": "Tortue %s et %s",
"block.computercraft.turtle_advanced": "Tortue avancée", "block.computercraft.turtle_advanced": "Tortue avancée",
"block.computercraft.turtle_advanced.upgraded": "Tortue %s avancée", "block.computercraft.turtle_advanced.upgraded": "Tortue %s avancée",
"block.computercraft.turtle_advanced.upgraded_twice": "Tortue %s %s avancée", "block.computercraft.turtle_advanced.upgraded_twice": "Tortue %s et %s avancée",
"item.computercraft.disk": "Disquette", "item.computercraft.disk": "Disquette",
"item.computercraft.treasure_disk": "Disquette", "item.computercraft.treasure_disk": "Disquette",
"item.computercraft.printed_page": "Page imprimée", "item.computercraft.printed_page": "Page imprimée",
@ -26,14 +26,14 @@
"item.computercraft.pocket_computer_normal": "Ordinateur de poche", "item.computercraft.pocket_computer_normal": "Ordinateur de poche",
"item.computercraft.pocket_computer_normal.upgraded": "Ordinateur de poche %s", "item.computercraft.pocket_computer_normal.upgraded": "Ordinateur de poche %s",
"item.computercraft.pocket_computer_advanced": "Ordinateur de poche avancé", "item.computercraft.pocket_computer_advanced": "Ordinateur de poche avancé",
"item.computercraft.pocket_computer_advanced.upgraded": "Ordinateur de poche %s avancé", "item.computercraft.pocket_computer_advanced.upgraded": "Ordinateur de poche avancé %s",
"upgrade.minecraft.diamond_sword.adjective": "combattante", "upgrade.minecraft.diamond_sword.adjective": "De Combat",
"upgrade.minecraft.diamond_shovel.adjective": "excavatrice", "upgrade.minecraft.diamond_shovel.adjective": "Excavatrice",
"upgrade.minecraft.diamond_pickaxe.adjective": "minière", "upgrade.minecraft.diamond_pickaxe.adjective": "Mineuse",
"upgrade.minecraft.diamond_axe.adjective": "forestière", "upgrade.minecraft.diamond_axe.adjective": "Bûcheronne",
"upgrade.minecraft.diamond_hoe.adjective": "agricole", "upgrade.minecraft.diamond_hoe.adjective": "Fermière",
"upgrade.minecraft.crafting_table.adjective": "ouvrière", "upgrade.minecraft.crafting_table.adjective": "Ouvrière",
"upgrade.computercraft.wireless_modem_normal.adjective": "sans fil", "upgrade.computercraft.wireless_modem_normal.adjective": "Sans Fil",
"upgrade.computercraft.wireless_modem_advanced.adjective": "de l'End", "upgrade.computercraft.wireless_modem_advanced.adjective": "de l'End",
"upgrade.computercraft.speaker.adjective": "Bruyante", "upgrade.computercraft.speaker.adjective": "Bruyante",
"chat.computercraft.wired_modem.peripheral_connected": "Le périphérique \"%s\" est connecté au réseau", "chat.computercraft.wired_modem.peripheral_connected": "Le périphérique \"%s\" est connecté au réseau",
@ -61,7 +61,7 @@
"commands.computercraft.track.stop.synopsis": "Arrêter la surveillance de tous les ordinateurs", "commands.computercraft.track.stop.synopsis": "Arrêter la surveillance de tous les ordinateurs",
"commands.computercraft.track.stop.desc": "Arrêter la surveillance des événements et des temps d'exécution", "commands.computercraft.track.stop.desc": "Arrêter la surveillance des événements et des temps d'exécution",
"commands.computercraft.track.stop.action": "Cliquez pour arrêter la surveillance", "commands.computercraft.track.stop.action": "Cliquez pour arrêter la surveillance",
"commands.computercraft.help.no_command": "Commande '%s' non reconnue", "commands.computercraft.help.no_command": "La commande '%s' n'existe pas",
"commands.computercraft.generic.no": "N", "commands.computercraft.generic.no": "N",
"commands.computercraft.generic.exception": "Exception non gérée (%s)", "commands.computercraft.generic.exception": "Exception non gérée (%s)",
"gui.computercraft.tooltip.disk_id": "ID de disque : %s", "gui.computercraft.tooltip.disk_id": "ID de disque : %s",

View File

@ -262,41 +262,48 @@ local g_tLuaKeywords = {
["while"] = true, ["while"] = true,
} }
local function serializeImpl(t, tTracking, sIndent) local function serialize_impl(t, tracking, indent, opts)
local sType = type(t) local sType = type(t)
if sType == "table" then if sType == "table" then
if tTracking[t] ~= nil then if tracking[t] ~= nil then
error("Cannot serialize table with recursive entries", 0) error("Cannot serialize table with recursive entries", 0)
end end
tTracking[t] = true tracking[t] = true
local result
if next(t) == nil then if next(t) == nil then
-- Empty tables are simple -- Empty tables are simple
return "{}" result = "{}"
else else
-- Other tables take more work -- Other tables take more work
local sResult = "{\n" local open, sub_indent, open_key, close_key, equal, comma = "{\n", indent .. " ", "[ ", " ] = ", " = ", ",\n"
local sSubIndent = sIndent .. " " if opts.compact then
local tSeen = {} open, sub_indent, open_key, close_key, equal, comma = "{", "", "[", "]=", "=", ","
end
result = open
local seen_keys = {}
for k, v in ipairs(t) do for k, v in ipairs(t) do
tSeen[k] = true seen_keys[k] = true
sResult = sResult .. sSubIndent .. serializeImpl(v, tTracking, sSubIndent) .. ",\n" result = result .. sub_indent .. serialize_impl(v, tracking, sub_indent, opts) .. comma
end end
for k, v in pairs(t) do for k, v in pairs(t) do
if not tSeen[k] then if not seen_keys[k] then
local sEntry local sEntry
if type(k) == "string" and not g_tLuaKeywords[k] and string.match(k, "^[%a_][%a%d_]*$") then if type(k) == "string" and not g_tLuaKeywords[k] and string.match(k, "^[%a_][%a%d_]*$") then
sEntry = k .. " = " .. serializeImpl(v, tTracking, sSubIndent) .. ",\n" sEntry = k .. equal .. serialize_impl(v, tracking, sub_indent, opts) .. comma
else else
sEntry = "[ " .. serializeImpl(k, tTracking, sSubIndent) .. " ] = " .. serializeImpl(v, tTracking, sSubIndent) .. ",\n" sEntry = open_key .. serialize_impl(k, tracking, sub_indent, opts) .. close_key .. serialize_impl(v, tracking, sub_indent, opts) .. comma
end end
sResult = sResult .. sSubIndent .. sEntry result = result .. sub_indent .. sEntry
end end
end end
sResult = sResult .. sIndent .. "}" result = result .. indent .. "}"
return sResult
end end
if opts.allow_repetitions then tracking[t] = nil end
return result
elseif sType == "string" then elseif sType == "string" then
return string.format("%q", t) return string.format("%q", t)
@ -645,17 +652,43 @@ do
end end
end end
--- Convert a Lua object into a textual representation, suitable for --[[- Convert a Lua object into a textual representation, suitable for
-- saving in a file or pretty-printing. saving in a file or pretty-printing.
--
-- @param t The object to serialise @param t The object to serialise
-- @treturn string The serialised representation @tparam { compact? = boolean, allow_repetitions? = boolean } opts Options for serialisation.
-- @throws If the object contains a value which cannot be - `compact`: Do not emit indentation and other whitespace between terms.
-- serialised. This includes functions and tables which appear multiple - `allow_repetitions`: Relax the check for recursive tables, allowing them to appear multiple
-- times. times (as long as tables do not appear inside themselves).
function serialize(t)
@treturn string The serialised representation
@throws If the object contains a value which cannot be
serialised. This includes functions and tables which appear multiple
times.
@see cc.pretty.pretty An alternative way to display a table, often more suitable for
pretty printing.
@usage Pretty print a basic table.
textutils.serialise({ 1, 2, 3, a = 1, ["another key"] = { true } })
@usage Demonstrates some of the other options
local tbl = { 1, 2, 3 }
print(textutils.serialize({ tbl, tbl }, { allow_repetitions = true }))
print(textutils.serialize(tbl, { compact = true }))
]]
function serialize(t, opts)
local tTracking = {} local tTracking = {}
return serializeImpl(t, tTracking, "") expect(2, opts, "table", "nil")
if opts then
field(opts, "compact", "boolean", "nil")
field(opts, "allow_repetitions", "boolean", "nil")
else
opts = {}
end
return serialize_impl(t, tTracking, "", opts)
end end
serialise = serialize -- GB version serialise = serialize -- GB version

View File

@ -151,6 +151,15 @@ local vector = {
tostring = function(self) tostring = function(self)
return self.x .. "," .. self.y .. "," .. self.z return self.x .. "," .. self.y .. "," .. self.z
end, end,
--- Check for equality between two vectors.
--
-- @tparam Vector self The first vector to compare.
-- @tparam Vector other The second vector to compare to.
-- @treturn boolean Whether or not the vectors are equal.
equals = function(self, other)
return self.x == other.x and self.y == other.y and self.z == other.z
end,
} }
local vmetatable = { local vmetatable = {
@ -161,6 +170,7 @@ local vmetatable = {
__div = vector.div, __div = vector.div,
__unm = vector.unm, __unm = vector.unm,
__tostring = vector.tostring, __tostring = vector.tostring,
__eq = vector.equals,
} }
--- Construct a new @{Vector} with the given coordinates. --- Construct a new @{Vector} with the given coordinates.

View File

@ -45,17 +45,19 @@ end
-- @type Doc -- @type Doc
local Doc = { } local Doc = { }
local function mk_doc(tbl) return setmetatable(tbl, Doc) end
--- An empty document. --- An empty document.
local empty = setmetatable({ tag = "nil" }, Doc) local empty = mk_doc({ tag = "nil" })
--- A document with a single space in it. --- A document with a single space in it.
local space = setmetatable({ tag = "text", text = " " }, Doc) local space = mk_doc({ tag = "text", text = " " })
--- A line break. When collapsed with @{group}, this will be replaced with @{empty}. --- A line break. When collapsed with @{group}, this will be replaced with @{empty}.
local line = setmetatable({ tag = "line", flat = empty }, Doc) local line = mk_doc({ tag = "line", flat = empty })
--- A line break. When collapsed with @{group}, this will be replaced with @{space}. --- A line break. When collapsed with @{group}, this will be replaced with @{space}.
local space_line = setmetatable({ tag = "line", flat = space }, Doc) local space_line = mk_doc({ tag = "line", flat = space })
local text_cache = { [""] = empty, [" "] = space, ["\n"] = space_line } local text_cache = { [""] = empty, [" "] = space, ["\n"] = space_line }

View File

@ -1,6 +1,8 @@
local function printUsage() local function printUsage()
local programName = arg[0] or fs.getName(shell.getRunningProgram()) local programName = arg[0] or fs.getName(shell.getRunningProgram())
print("Usage: " .. programName .. " <name> <program> <arguments>") print("Usage:")
print(" " .. programName .. " <name> <program> <arguments>")
print(" " .. programName .. " scale <name> <scale>")
return return
end end
@ -10,6 +12,23 @@ if #tArgs < 2 then
return return
end end
if tArgs[1] == "scale" then
local sName = tArgs[2]
if peripheral.getType(sName) ~= "monitor" then
print("No monitor named " .. sName)
return
end
local nRes = tonumber(tArgs[3])
if nRes == nil or nRes < 0.5 or nRes > 5 then
print("Invalid scale: " .. nRes)
return
end
peripheral.call(sName, "setTextScale", nRes)
return
end
local sName = tArgs[1] local sName = tArgs[1]
if peripheral.getType(sName) ~= "monitor" then if peripheral.getType(sName) ~= "monitor" then
print("No monitor named " .. sName) print("No monitor named " .. sName)

View File

@ -67,10 +67,25 @@ shell.setCompletionFunction("rom/programs/label.lua", completion.build(
)) ))
shell.setCompletionFunction("rom/programs/list.lua", completion.build(completion.dir)) shell.setCompletionFunction("rom/programs/list.lua", completion.build(completion.dir))
shell.setCompletionFunction("rom/programs/mkdir.lua", completion.build({ completion.dir, many = true })) shell.setCompletionFunction("rom/programs/mkdir.lua", completion.build({ completion.dir, many = true }))
local complete_monitor_extra = { "scale" }
shell.setCompletionFunction("rom/programs/monitor.lua", completion.build( shell.setCompletionFunction("rom/programs/monitor.lua", completion.build(
{ completion.peripheral, true }, function(shell, text, previous)
completion.program local choices = completion.peripheral(shell, text, previous, true)
for _, option in pairs(completion.choice(shell, text, previous, complete_monitor_extra, true)) do
choices[#choices + 1] = option
end
return choices
end,
function(shell, text, previous)
if previous[2] == "scale" then
return completion.peripheral(shell, text, previous, true)
else
return completion.program(shell, text, previous)
end
end
)) ))
shell.setCompletionFunction("rom/programs/move.lua", completion.build( shell.setCompletionFunction("rom/programs/move.lua", completion.build(
{ completion.dirOrFile, true }, { completion.dirOrFile, true },
completion.dirOrFile completion.dirOrFile

View File

@ -0,0 +1,52 @@
/*
* This file is part of ComputerCraft - http://www.computercraft.info
* Copyright Daniel Ratcliffe, 2011-2021. Do not distribute without permission.
* Send enquiries to dratcliffe@gmail.com
*/
package dan200.computercraft.core.terminal;
import dan200.computercraft.ContramapMatcher;
import org.hamcrest.Matcher;
import org.hamcrest.Matchers;
import java.util.Arrays;
public class TerminalMatchers
{
public static Matcher<Terminal> textColourMatches( String[] x )
{
return linesMatch( "text colour", Terminal::getTextColourLine, x );
}
public static Matcher<Terminal> backgroundColourMatches( String[] x )
{
return linesMatch( "background colour", Terminal::getBackgroundColourLine, x );
}
public static Matcher<Terminal> textMatches( String[] x )
{
return linesMatch( "text", Terminal::getLine, x );
}
@SuppressWarnings( "unchecked" )
public static Matcher<Terminal> linesMatch( String kind, LineProvider getLine, String[] lines )
{
return linesMatchWith( kind, getLine, Arrays.stream( lines ).map( Matchers::equalTo ).toArray( Matcher[]::new ) );
}
public static Matcher<Terminal> linesMatchWith( String kind, LineProvider getLine, Matcher<String>[] lines )
{
return new ContramapMatcher<>( kind, terminal -> {
String[] termLines = new String[terminal.getHeight()];
for( int i = 0; i < termLines.length; i++ ) termLines[i] = getLine.getLine( terminal, i ).toString();
return termLines;
}, Matchers.array( lines ) );
}
@FunctionalInterface
public interface LineProvider
{
TextBuffer getLine( Terminal terminal, int line );
}
}

View File

@ -0,0 +1,717 @@
/*
* This file is part of ComputerCraft - http://www.computercraft.info
* Copyright Daniel Ratcliffe, 2011-2021. Do not distribute without permission.
* Send enquiries to dratcliffe@gmail.com
*/
package dan200.computercraft.core.terminal;
import dan200.computercraft.shared.util.Colour;
import dan200.computercraft.utils.CallCounter;
import io.netty.buffer.Unpooled;
import net.minecraft.nbt.CompoundNBT;
import net.minecraft.network.PacketBuffer;
import org.hamcrest.Matcher;
import org.junit.jupiter.api.Test;
import static dan200.computercraft.core.terminal.TerminalMatchers.*;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.allOf;
import static org.junit.jupiter.api.Assertions.*;
class TerminalTest
{
@Test
void testCreation()
{
Terminal terminal = new Terminal( 16, 9 );
assertEquals( 16, terminal.getWidth() );
assertEquals( 9, terminal.getHeight() );
}
@Test
void testSetAndGetLine()
{
Terminal terminal = new Terminal( 16, 9 );
terminal.setLine( 1, "ABCDEFGHIJKLMNOP", "0123456789abcdef", "fedcba9876543210" );
assertEquals( "ABCDEFGHIJKLMNOP", terminal.getLine( 1 ).toString() );
assertEquals( "0123456789abcdef", terminal.getTextColourLine( 1 ).toString() );
assertEquals( "fedcba9876543210", terminal.getBackgroundColourLine( 1 ).toString() );
}
@Test
void testGetLineOutOfBounds()
{
Terminal terminal = new Terminal( 16, 9 );
assertNull( terminal.getLine( -5 ) );
assertNull( terminal.getLine( 12 ) );
assertNull( terminal.getTextColourLine( -5 ) );
assertNull( terminal.getTextColourLine( 12 ) );
assertNull( terminal.getBackgroundColourLine( -5 ) );
assertNull( terminal.getBackgroundColourLine( 12 ) );
}
@Test
void testDefaults()
{
Terminal terminal = new Terminal( 16, 9 );
assertEquals( 0, terminal.getCursorX() );
assertEquals( 0, terminal.getCursorY() );
assertFalse( terminal.getCursorBlink() );
assertEquals( 0, terminal.getTextColour() );
assertEquals( 15, terminal.getBackgroundColour() );
}
@Test
void testDefaultTextBuffer()
{
assertThat( new Terminal( 4, 3 ), textMatches( new String[] {
" ",
" ",
" ",
} ) );
}
@Test
void testDefaultTextColourBuffer()
{
assertThat( new Terminal( 4, 3 ), textColourMatches( new String[] {
"0000",
"0000",
"0000",
} ) );
}
@Test
void testDefaultBackgroundColourBuffer()
{
assertThat( new Terminal( 4, 3 ), TerminalMatchers.backgroundColourMatches( new String[] {
"ffff",
"ffff",
"ffff",
} ) );
}
@Test
void testZeroSizeBuffers()
{
String[] x = new String[0];
assertThat( new Terminal( 0, 0 ), allOf(
textMatches( new String[0] ),
textColourMatches( x ),
TerminalMatchers.backgroundColourMatches( x )
) );
}
@Test
void testResizeWidthAndHeight()
{
CallCounter callCounter = new CallCounter();
Terminal terminal = new Terminal( 4, 3, callCounter );
terminal.setLine( 0, "test", "aaaa", "eeee" );
callCounter.reset();
terminal.resize( 5, 4 );
assertThat( terminal, allOf(
textMatches( new String[] {
"test ",
" ",
" ",
" ",
} ),
textColourMatches( new String[] {
"aaaa0",
"00000",
"00000",
"00000",
} ), TerminalMatchers.backgroundColourMatches( new String[] {
"eeeef",
"fffff",
"fffff",
"fffff",
} )
) );
callCounter.assertCalledTimes( 1 );
}
@Test
void testResizeSmaller()
{
CallCounter callCounter = new CallCounter();
Terminal terminal = new Terminal( 4, 3, callCounter );
terminal.setLine( 0, "test", "aaaa", "eeee" );
terminal.setLine( 1, "smol", "aaaa", "eeee" );
terminal.setLine( 2, "term", "aaaa", "eeee" );
callCounter.reset();
terminal.resize( 2, 2 );
assertThat( terminal, allOf(
textMatches( new String[] {
"te",
"sm",
} ),
textColourMatches( new String[] {
"aa",
"aa",
} ),
TerminalMatchers.backgroundColourMatches( new String[] {
"ee",
"ee",
} )
) );
callCounter.assertCalledTimes( 1 );
}
@Test
void testResizeWithSameDimensions()
{
CallCounter callCounter = new CallCounter();
Terminal terminal = new Terminal( 4, 3, callCounter );
TerminalBufferSnapshot old = new TerminalBufferSnapshot( terminal );
terminal.resize( 4, 3 );
assertThat( "Terminal should be unchanged", terminal, old.matches() );
callCounter.assertNotCalled();
}
@Test
void testSetAndGetCursorPos()
{
CallCounter callCounter = new CallCounter();
Terminal terminal = new Terminal( 4, 3, callCounter );
terminal.setCursorPos( 2, 1 );
assertEquals( 2, terminal.getCursorX() );
assertEquals( 1, terminal.getCursorY() );
callCounter.assertCalledTimes( 1 );
}
@Test
void testSetCursorPosUnchanged()
{
CallCounter callCounter = new CallCounter();
Terminal terminal = new Terminal( 4, 3, callCounter );
terminal.setCursorPos( 2, 1 );
callCounter.reset();
terminal.setCursorPos( 2, 1 );
assertEquals( 2, terminal.getCursorX() );
assertEquals( 1, terminal.getCursorY() );
callCounter.assertNotCalled();
}
@Test
void testSetCursorBlink()
{
CallCounter callCounter = new CallCounter();
Terminal terminal = new Terminal( 4, 3, callCounter );
terminal.setCursorBlink( true );
assertTrue( terminal.getCursorBlink() );
callCounter.assertCalledTimes( 1 );
}
@Test
void testSetCursorBlinkUnchanged()
{
CallCounter callCounter = new CallCounter();
Terminal terminal = new Terminal( 4, 3, callCounter );
terminal.setCursorBlink( true );
callCounter.reset();
terminal.setCursorBlink( true );
assertTrue( terminal.getCursorBlink() );
callCounter.assertNotCalled();
}
@Test
void testSetTextColour()
{
CallCounter callCounter = new CallCounter();
Terminal terminal = new Terminal( 4, 3, callCounter );
terminal.setTextColour( 5 );
assertEquals( terminal.getTextColour(), 5 );
callCounter.assertCalledTimes( 1 );
}
@Test
void testSetTextColourUnchanged()
{
CallCounter callCounter = new CallCounter();
Terminal terminal = new Terminal( 4, 3, callCounter );
terminal.setTextColour( 5 );
callCounter.reset();
terminal.setTextColour( 5 );
assertEquals( terminal.getTextColour(), 5 );
callCounter.assertNotCalled();
}
@Test
void testSetBackgroundColour()
{
CallCounter callCounter = new CallCounter();
Terminal terminal = new Terminal( 4, 3, callCounter );
terminal.setBackgroundColour( 5 );
assertEquals( terminal.getBackgroundColour(), 5 );
callCounter.assertCalledTimes( 1 );
}
@Test
void testSetBackgroundColourUnchanged()
{
CallCounter callCounter = new CallCounter();
Terminal terminal = new Terminal( 4, 3, callCounter );
terminal.setBackgroundColour( 5 );
callCounter.reset();
terminal.setBackgroundColour( 5 );
assertEquals( terminal.getBackgroundColour(), 5 );
callCounter.assertNotCalled();
}
@Test
void testBlitFromOrigin()
{
CallCounter callCounter = new CallCounter();
Terminal terminal = new Terminal( 4, 3, callCounter );
terminal.blit( "test", "1234", "abcd" );
assertThat( terminal, allOf(
textMatches( new String[] {
"test",
" ",
" ",
} ), textColourMatches( new String[] {
"1234",
"0000",
"0000",
} ), backgroundColourMatches( new String[] {
"abcd",
"ffff",
"ffff",
} )
) );
callCounter.assertCalledTimes( 1 );
}
@Test
void testBlitWithOffset()
{
CallCounter callCounter = new CallCounter();
Terminal terminal = new Terminal( 4, 3, callCounter );
terminal.setCursorPos( 2, 1 );
callCounter.reset();
terminal.blit( "hi", "11", "ee" );
assertThat( terminal, allOf(
textMatches( new String[] {
" ",
" hi",
" ",
} ),
textColourMatches( new String[] {
"0000",
"0011",
"0000",
} ),
backgroundColourMatches( new String[] {
"ffff",
"ffee",
"ffff",
} )
) );
callCounter.assertCalledTimes( 1 );
}
@Test
void testBlitOutOfBounds()
{
CallCounter callCounter = new CallCounter();
Terminal terminal = new Terminal( 4, 3, callCounter );
TerminalBufferSnapshot old = new TerminalBufferSnapshot( terminal );
terminal.setCursorPos( 2, -5 );
callCounter.reset();
terminal.blit( "hi", "11", "ee" );
assertThat( terminal, old.matches() );
callCounter.assertNotCalled();
terminal.setCursorPos( 2, 5 );
callCounter.reset();
terminal.blit( "hi", "11", "ee" );
assertThat( terminal, old.matches() );
callCounter.assertNotCalled();
}
@Test
void testWriteFromOrigin()
{
CallCounter callCounter = new CallCounter();
Terminal terminal = new Terminal( 4, 3, callCounter );
terminal.write( "test" );
assertThat( terminal, allOf(
textMatches( new String[] {
"test",
" ",
" ",
} ), textColourMatches( new String[] {
"0000",
"0000",
"0000",
} ), backgroundColourMatches( new String[] {
"ffff",
"ffff",
"ffff",
} )
) );
callCounter.assertCalledTimes( 1 );
}
@Test
void testWriteWithOffset()
{
CallCounter callCounter = new CallCounter();
Terminal terminal = new Terminal( 4, 3, callCounter );
terminal.setCursorPos( 2, 1 );
callCounter.reset();
terminal.write( "hi" );
assertThat( terminal, allOf(
textMatches( new String[] {
" ",
" hi",
" ",
} ),
textColourMatches( new String[] {
"0000",
"0000",
"0000",
} ),
backgroundColourMatches( new String[] {
"ffff",
"ffff",
"ffff",
} )
) );
callCounter.assertCalledTimes( 1 );
}
@Test
void testWriteOutOfBounds()
{
CallCounter callCounter = new CallCounter();
Terminal terminal = new Terminal( 4, 3, callCounter );
TerminalBufferSnapshot old = new TerminalBufferSnapshot( terminal );
terminal.setCursorPos( 2, -5 );
callCounter.reset();
terminal.write( "hi" );
assertThat( terminal, old.matches() );
callCounter.assertNotCalled();
terminal.setCursorPos( 2, 5 );
callCounter.reset();
terminal.write( "hi" );
assertThat( terminal, old.matches() );
callCounter.assertNotCalled();
}
@Test
void testScrollUp()
{
CallCounter callCounter = new CallCounter();
Terminal terminal = new Terminal( 4, 3, callCounter );
terminal.setLine( 1, "test", "1111", "eeee" );
callCounter.reset();
terminal.scroll( 1 );
assertThat( terminal, allOf(
textMatches( new String[] {
"test",
" ",
" ",
} ),
textColourMatches( new String[] {
"1111",
"0000",
"0000",
} ),
backgroundColourMatches( new String[] {
"eeee",
"ffff",
"ffff",
} )
) );
callCounter.assertCalledTimes( 1 );
}
@Test
void testScrollDown()
{
CallCounter callCounter = new CallCounter();
Terminal terminal = new Terminal( 4, 3, callCounter );
terminal.setLine( 1, "test", "1111", "eeee" );
callCounter.reset();
terminal.scroll( -1 );
assertThat( terminal, allOf(
textMatches( new String[] {
" ",
" ",
"test",
} ),
textColourMatches( new String[] {
"0000",
"0000",
"1111",
} ),
backgroundColourMatches( new String[] {
"ffff",
"ffff",
"eeee",
} )
) );
callCounter.assertCalledTimes( 1 );
}
@Test
void testScrollZeroLinesUnchanged()
{
CallCounter callCounter = new CallCounter();
Terminal terminal = new Terminal( 4, 3, callCounter );
terminal.setLine( 1, "test", "1111", "eeee" );
TerminalBufferSnapshot old = new TerminalBufferSnapshot( terminal );
callCounter.reset();
terminal.scroll( 0 );
assertThat( terminal, old.matches() );
callCounter.assertNotCalled();
}
@Test
void testClear()
{
CallCounter callCounter = new CallCounter();
Terminal terminal = new Terminal( 4, 3, callCounter );
TerminalBufferSnapshot old = new TerminalBufferSnapshot( terminal );
terminal.setLine( 1, "test", "1111", "eeee" );
callCounter.reset();
terminal.clear();
assertThat( terminal, old.matches() );
callCounter.assertCalledTimes( 1 );
}
@Test
void testClearLine()
{
CallCounter callCounter = new CallCounter();
Terminal terminal = new Terminal( 4, 3, callCounter );
TerminalBufferSnapshot old = new TerminalBufferSnapshot( terminal );
terminal.setLine( 1, "test", "1111", "eeee" );
terminal.setCursorPos( 0, 1 );
callCounter.reset();
terminal.clearLine();
assertThat( terminal, old.matches() );
callCounter.assertCalledTimes( 1 );
}
@Test
void testClearLineOutOfBounds()
{
CallCounter callCounter = new CallCounter();
Terminal terminal = new Terminal( 4, 3, callCounter );
TerminalBufferSnapshot old;
terminal.setLine( 1, "test", "1111", "eeee" );
old = new TerminalBufferSnapshot( terminal );
terminal.setCursorPos( 0, -5 );
callCounter.reset();
terminal.clearLine();
assertThat( terminal, old.matches() );
callCounter.assertNotCalled();
terminal.setLine( 1, "test", "1111", "eeee" );
old = new TerminalBufferSnapshot( terminal );
terminal.setCursorPos( 0, 5 );
callCounter.reset();
terminal.clearLine();
assertThat( terminal, old.matches() );
callCounter.assertNotCalled();
}
@Test
void testPacketBufferRoundtrip()
{
Terminal writeTerminal = new Terminal( 2, 1 );
writeTerminal.blit( "hi", "11", "ee" );
writeTerminal.setCursorPos( 2, 5 );
writeTerminal.setTextColour( 3 );
writeTerminal.setBackgroundColour( 5 );
PacketBuffer packetBuffer = new PacketBuffer( Unpooled.buffer() );
writeTerminal.write( packetBuffer );
CallCounter callCounter = new CallCounter();
Terminal readTerminal = new Terminal( 2, 1, callCounter );
packetBuffer.writeBytes( packetBuffer );
readTerminal.read( packetBuffer );
assertThat( readTerminal, allOf(
textMatches( new String[] { "hi", } ),
textColourMatches( new String[] { "11", } ),
backgroundColourMatches( new String[] { "ee", } )
) );
assertEquals( 2, readTerminal.getCursorX() );
assertEquals( 5, readTerminal.getCursorY() );
assertEquals( 3, readTerminal.getTextColour() );
assertEquals( 5, readTerminal.getBackgroundColour() );
callCounter.assertCalledTimes( 1 );
}
@Test
void testNbtRoundtrip()
{
Terminal writeTerminal = new Terminal( 10, 5 );
writeTerminal.blit( "hi", "11", "ee" );
writeTerminal.setCursorPos( 2, 5 );
writeTerminal.setTextColour( 3 );
writeTerminal.setBackgroundColour( 5 );
CompoundNBT nbt = new CompoundNBT();
writeTerminal.writeToNBT( nbt );
CallCounter callCounter = new CallCounter();
Terminal readTerminal = new Terminal( 2, 1, callCounter );
readTerminal.readFromNBT( nbt );
assertThat( readTerminal, allOf(
textMatches( new String[] { "hi", } ),
textColourMatches( new String[] { "11", } ),
backgroundColourMatches( new String[] { "ee", } )
) );
assertEquals( 2, readTerminal.getCursorX() );
assertEquals( 5, readTerminal.getCursorY() );
assertEquals( 3, readTerminal.getTextColour() );
assertEquals( 5, readTerminal.getBackgroundColour() );
callCounter.assertCalledTimes( 1 );
}
@Test
void testReadWriteNBTEmpty()
{
Terminal terminal = new Terminal( 0, 0 );
CompoundNBT nbt = new CompoundNBT();
terminal.writeToNBT( nbt );
CallCounter callCounter = new CallCounter();
terminal = new Terminal( 0, 1, callCounter );
terminal.readFromNBT( nbt );
assertThat( terminal, allOf(
textMatches( new String[] { "", } ),
textColourMatches( new String[] { "", } ),
backgroundColourMatches( new String[] { "", } )
) );
assertEquals( 0, terminal.getCursorX() );
assertEquals( 0, terminal.getCursorY() );
assertEquals( 0, terminal.getTextColour() );
assertEquals( 15, terminal.getBackgroundColour() );
callCounter.assertCalledTimes( 1 );
}
@Test
void testGetColour()
{
// 0 - 9
assertEquals( 0, Terminal.getColour( '0', Colour.WHITE ) );
assertEquals( 1, Terminal.getColour( '1', Colour.WHITE ) );
assertEquals( 8, Terminal.getColour( '8', Colour.WHITE ) );
assertEquals( 9, Terminal.getColour( '9', Colour.WHITE ) );
// a - f
assertEquals( 10, Terminal.getColour( 'a', Colour.WHITE ) );
assertEquals( 11, Terminal.getColour( 'b', Colour.WHITE ) );
assertEquals( 14, Terminal.getColour( 'e', Colour.WHITE ) );
assertEquals( 15, Terminal.getColour( 'f', Colour.WHITE ) );
// char out of bounds -> use colour enum ordinal
assertEquals( 0, Terminal.getColour( 'z', Colour.WHITE ) );
assertEquals( 0, Terminal.getColour( '!', Colour.WHITE ) );
assertEquals( 0, Terminal.getColour( 'Z', Colour.WHITE ) );
assertEquals( 5, Terminal.getColour( 'Z', Colour.LIME ) );
}
private static final class TerminalBufferSnapshot
{
final String[] textLines;
final String[] textColourLines;
final String[] backgroundColourLines;
private TerminalBufferSnapshot( Terminal terminal )
{
textLines = new String[terminal.getHeight()];
textColourLines = new String[terminal.getHeight()];
backgroundColourLines = new String[terminal.getHeight()];
for( int i = 0; i < terminal.getHeight(); i++ )
{
textLines[i] = terminal.getLine( i ).toString();
textColourLines[i] = terminal.getTextColourLine( i ).toString();
backgroundColourLines[i] = terminal.getBackgroundColourLine( i ).toString();
}
}
public Matcher<Terminal> matches()
{
return allOf(
textMatches( textLines ), textColourMatches( textColourLines ), TerminalMatchers.backgroundColourMatches( backgroundColourLines )
);
}
}
}

View File

@ -5,7 +5,7 @@ import dan200.computercraft.ingame.api.TestContext
import dan200.computercraft.ingame.api.checkComputerOk import dan200.computercraft.ingame.api.checkComputerOk
class TurtleTest { class TurtleTest {
@GameTest(required = false) @GameTest
suspend fun `Unequip refreshes peripheral`(context: TestContext) = context.checkComputerOk(1) suspend fun `Unequip refreshes peripheral`(context: TestContext) = context.checkComputerOk(1)
/** /**
@ -33,7 +33,7 @@ class TurtleTest {
suspend fun `Place waterlogged`(context: TestContext) = context.checkComputerOk(7) suspend fun `Place waterlogged`(context: TestContext) = context.checkComputerOk(7)
/** /**
* Checks turtles can place when waterlogged. * Checks turtles can pick up lava
* *
* @see [#297](https://github.com/SquidDev-CC/CC-Tweaked/issues/297) * @see [#297](https://github.com/SquidDev-CC/CC-Tweaked/issues/297)
*/ */
@ -41,7 +41,7 @@ class TurtleTest {
suspend fun `Gather lava`(context: TestContext) = context.checkComputerOk(8) suspend fun `Gather lava`(context: TestContext) = context.checkComputerOk(8)
/** /**
* Checks turtles can place when waterlogged. * Checks turtles can hoe dirt.
* *
* @see [#258](https://github.com/SquidDev-CC/CC-Tweaked/issues/258) * @see [#258](https://github.com/SquidDev-CC/CC-Tweaked/issues/258)
*/ */
@ -57,9 +57,15 @@ class TurtleTest {
suspend fun `Place monitor`(context: TestContext) = context.checkComputerOk(10) suspend fun `Place monitor`(context: TestContext) = context.checkComputerOk(10)
/** /**
* Checks computers can place into compostors. These are non-typical inventories, so * Checks turtles can place into compostors. These are non-typical inventories, so
* worth ensuring. * worth testing.
*/ */
@GameTest @GameTest
suspend fun `Use compostors`(context: TestContext) = context.checkComputerOk(11) suspend fun `Use compostors`(context: TestContext) = context.checkComputerOk(11)
/**
* Checks turtles can be cleaned in cauldrons.
*/
@GameTest
suspend fun `Cleaned with cauldrons`(context: TestContext) = context.checkComputerOk(12)
} }

View File

@ -0,0 +1,34 @@
/*
* This file is part of ComputerCraft - http://www.computercraft.info
* Copyright Daniel Ratcliffe, 2011-2021. Do not distribute without permission.
* Send enquiries to dratcliffe@gmail.com
*/
package dan200.computercraft.utils;
import static org.junit.jupiter.api.Assertions.assertEquals;
public class CallCounter implements Runnable
{
private int timesCalled = 0;
@Override
public void run()
{
timesCalled++;
}
public void assertCalledTimes( int expectedTimesCalled )
{
assertEquals( expectedTimesCalled, timesCalled, "Callback was not called the correct number of times" );
}
public void assertNotCalled()
{
assertEquals( 0, timesCalled, "Should never have been called." );
}
public void reset()
{
this.timesCalled = 0;
}
}

View File

@ -15,3 +15,11 @@ function test.eq(expected, actual, msg)
if msg then message = ("%s - %s"):format(msg, message) end if msg then message = ("%s - %s"):format(msg, message) end
test.fail(message) test.fail(message)
end end
function test.neq(expected, actual, msg)
if expected ~= actual then return end
local message = ("Assertion failed:\nExpected something different to %s"):format(expected)
if msg then message = ("%s - %s"):format(msg, message) end
test.fail(message)
end

View File

@ -62,6 +62,42 @@ describe("The textutils library", function()
end) end)
end) end)
describe("textutils.serialise", function()
it("serialises basic tables", function()
expect(textutils.serialise({ 1, 2, 3, a = 1, b = {} }))
:eq("{\n 1,\n 2,\n 3,\n a = 1,\n b = {},\n}")
end)
it("fails on recursive tables", function()
local rep = {}
expect.error(textutils.serialise, { rep, rep }):eq("Cannot serialize table with recursive entries")
local rep2 = { 1 }
expect.error(textutils.serialise, { rep2, rep2 }):eq("Cannot serialize table with recursive entries")
local recurse = {}
recurse[1] = recurse
expect.error(textutils.serialise, recurse):eq("Cannot serialize table with recursive entries")
end)
it("can allow repeated tables", function()
local rep = {}
expect(textutils.serialise({ rep, rep }, { allow_repetitions = true })):eq("{\n {},\n {},\n}")
local rep2 = { 1 }
expect(textutils.serialise({ rep2, rep2 }, { allow_repetitions = true })):eq("{\n {\n 1,\n },\n {\n 1,\n },\n}")
local recurse = {}
recurse[1] = recurse
expect.error(textutils.serialise, recurse, { allow_repetitions = true }):eq("Cannot serialize table with recursive entries")
end)
it("can emit in a compact form", function()
expect(textutils.serialise({ 1, 2, 3, a = 1, [false] = {} }, { compact = true }))
:eq("{1,2,3,a=1,[false]={},}")
end)
end)
describe("textutils.unserialise", function() describe("textutils.unserialise", function()
it("validates arguments", function() it("validates arguments", function()
textutils.unserialise("") textutils.unserialise("")

View File

@ -3,6 +3,22 @@ local capture = require "test_helpers".capture_program
describe("The monitor program", function() describe("The monitor program", function()
it("displays its usage when given no arguments", function() it("displays its usage when given no arguments", function()
expect(capture(stub, "monitor")) expect(capture(stub, "monitor"))
:matches { ok = true, output = "Usage: monitor <name> <program> <arguments>\n", error = "" } :matches {
ok = true,
output =
"Usage:\n" ..
" monitor <name> <program> <arguments>\n" ..
" monitor scale <name> <scale>\n",
error = "",
}
end)
it("changes the text scale with the scale command", function()
local r = 1
stub(peripheral, "call", function(s, f, t) r = t end)
stub(peripheral, "getType", function() return "monitor" end)
expect(capture(stub, "monitor", "scale", "left", "0.5"))
:matches { ok = true, output = "", error = "" }
expect(r):equals(0.5)
end) end)
end) end)

View File

@ -0,0 +1,10 @@
local old_details = turtle.getItemDetail(1, true)
test.assert(turtle.place(), "Dyed turtle")
local new_details = turtle.getItemDetail(1, true)
test.eq("computercraft:turtle_normal", new_details.name, "Still a turtle")
test.neq(old_details.nbt, new_details.nbt, "Colour has changed")
test.ok()

View File

@ -1,3 +1,3 @@
{ {
"computer": 11 "computer": 12
} }

View File

@ -0,0 +1,163 @@
{
size: [3, 3, 3],
entities: [],
blocks: [
{
pos: [0, 0, 0],
state: 0
},
{
pos: [1, 0, 0],
state: 0
},
{
pos: [2, 0, 0],
state: 0
},
{
pos: [0, 0, 1],
state: 0
},
{
pos: [1, 0, 1],
state: 0
},
{
pos: [2, 0, 1],
state: 0
},
{
pos: [0, 0, 2],
state: 0
},
{
pos: [1, 0, 2],
state: 0
},
{
pos: [2, 0, 2],
state: 0
},
{
nbt: {
Owner: {
UpperId: 4039158846114182220L,
LowerId: -6876936588741668278L,
Name: "Dev"
},
Fuel: 0,
Label: "Clean turtle",
Slot: 0,
Items: [
{
Slot: 0b,
id: "computercraft:turtle_normal",
Count: 1b,
tag: {
display: {
Name: '{"text":"Clean turtle"}'
},
Color: 13388876,
ComputerId: 12
}
}
],
id: "computercraft:turtle_normal",
ComputerId: 12,
On: 1b
},
pos: [1, 1, 0],
state: 1
},
{
pos: [0, 1, 0],
state: 2
},
{
pos: [2, 1, 0],
state: 2
},
{
pos: [0, 2, 0],
state: 2
},
{
pos: [1, 2, 0],
state: 2
},
{
pos: [2, 2, 0],
state: 2
},
{
pos: [0, 1, 1],
state: 2
},
{
pos: [1, 1, 1],
state: 3
},
{
pos: [2, 1, 1],
state: 2
},
{
pos: [0, 2, 1],
state: 2
},
{
pos: [1, 2, 1],
state: 2
},
{
pos: [2, 2, 1],
state: 2
},
{
pos: [0, 1, 2],
state: 2
},
{
pos: [1, 1, 2],
state: 2
},
{
pos: [2, 1, 2],
state: 2
},
{
pos: [0, 2, 2],
state: 2
},
{
pos: [1, 2, 2],
state: 2
},
{
pos: [2, 2, 2],
state: 2
}
],
palette: [
{
Name: "minecraft:polished_andesite"
},
{
Properties: {
waterlogged: "false",
facing: "south"
},
Name: "computercraft:turtle_normal"
},
{
Name: "minecraft:air"
},
{
Properties: {
level: "3"
},
Name: "minecraft:cauldron"
}
],
DataVersion: 2230
}

View File

@ -10,12 +10,12 @@ table.pretty-table td, table.pretty-table th {
} }
table.pretty-table th { table.pretty-table th {
background-color: #f0f0f0; background-color: var(--background-2);
} }
pre.highlight.highlight-lua { pre.highlight.highlight-lua {
position: relative; position: relative;
background: #eee; background: var(--background-2);
padding: 2px; padding: 2px;
} }