mirror of
https://github.com/SquidDev-CC/CC-Tweaked
synced 2024-12-14 04:00:30 +00:00
Merge branch 'mc-1.15.x' into mc-1.16.x
This commit is contained in:
commit
f38a6a9d43
103
build.gradle
103
build.gradle
@ -15,7 +15,6 @@ buildscript {
|
||||
dependencies {
|
||||
classpath 'com.google.code.gson:gson:2.8.1'
|
||||
classpath 'net.minecraftforge.gradle:ForgeGradle:4.1.9'
|
||||
classpath 'net.sf.proguard:proguard-gradle:6.1.0beta2'
|
||||
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 {
|
||||
inputs.property "version", mod_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
|
||||
|
||||
import org.apache.tools.ant.taskdefs.condition.Os
|
||||
@ -399,6 +302,10 @@ jacocoTestReport {
|
||||
|
||||
check.dependsOn jacocoTestReport
|
||||
|
||||
|
||||
import com.hierynomus.gradle.license.tasks.LicenseCheck
|
||||
import com.hierynomus.gradle.license.tasks.LicenseFormat
|
||||
|
||||
license {
|
||||
mapping("java", "SLASHSTAR_STYLE")
|
||||
strictCheck true
|
||||
@ -452,7 +359,7 @@ task setupServer(type: Copy) {
|
||||
tasks.register('testInGame', JavaExec.class).configure {
|
||||
it.group('test 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
|
||||
// isn't created until the task is configured (which is no good for us).
|
||||
|
39
src/main/java/dan200/computercraft/client/ClientHooks.java
Normal file
39
src/main/java/dan200/computercraft/client/ClientHooks.java
Normal 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();
|
||||
}
|
||||
}
|
@ -6,31 +6,39 @@
|
||||
package dan200.computercraft.client;
|
||||
|
||||
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.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.media.items.ItemDisk;
|
||||
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.util.Colour;
|
||||
import net.minecraft.client.renderer.model.IBakedModel;
|
||||
import net.minecraft.client.renderer.model.IUnbakedModel;
|
||||
import net.minecraft.client.gui.ScreenManager;
|
||||
import net.minecraft.client.renderer.RenderType;
|
||||
import net.minecraft.client.renderer.RenderTypeLookup;
|
||||
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.minecraftforge.api.distmarker.Dist;
|
||||
import net.minecraftforge.client.event.ColorHandlerEvent;
|
||||
import net.minecraftforge.client.event.ModelBakeEvent;
|
||||
import net.minecraftforge.client.event.ModelRegistryEvent;
|
||||
import net.minecraftforge.client.event.TextureStitchEvent;
|
||||
import net.minecraftforge.client.model.ModelLoader;
|
||||
import net.minecraftforge.client.model.ModelLoaderRegistry;
|
||||
import net.minecraftforge.client.model.SimpleModelTransform;
|
||||
import net.minecraftforge.eventbus.api.SubscribeEvent;
|
||||
import net.minecraftforge.fml.client.registry.RenderingRegistry;
|
||||
import net.minecraftforge.fml.common.Mod;
|
||||
import net.minecraftforge.fml.event.lifecycle.FMLClientSetupEvent;
|
||||
|
||||
import java.util.HashSet;
|
||||
import java.util.Map;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
/**
|
||||
* Registers textures and models for items.
|
||||
@ -39,6 +47,7 @@ import java.util.Map;
|
||||
public final class ClientRegistry
|
||||
{
|
||||
private static final String[] EXTRA_MODELS = new String[] {
|
||||
// Turtle upgrades
|
||||
"turtle_modem_normal_off_left",
|
||||
"turtle_modem_normal_on_left",
|
||||
"turtle_modem_normal_off_right",
|
||||
@ -54,56 +63,20 @@ public final class ClientRegistry
|
||||
"turtle_speaker_upgrade_left",
|
||||
"turtle_speaker_upgrade_right",
|
||||
|
||||
// Turtle block renderer
|
||||
"turtle_colour",
|
||||
"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() {}
|
||||
|
||||
@SubscribeEvent
|
||||
public static void registerModels( ModelRegistryEvent event )
|
||||
{
|
||||
ModelLoaderRegistry.registerLoader( new ResourceLocation( ComputerCraft.MOD_ID, "turtle" ), TurtleModelLoader.INSTANCE );
|
||||
}
|
||||
|
||||
@SubscribeEvent
|
||||
public static void onTextureStitchEvent( TextureStitchEvent.Pre event )
|
||||
{
|
||||
if( !event.getMap().location().equals( PlayerContainer.BLOCK_ATLAS ) ) return;
|
||||
|
||||
for( String extra : EXTRA_TEXTURES )
|
||||
for( String model : EXTRA_MODELS )
|
||||
{
|
||||
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 );
|
||||
}
|
||||
ModelLoader.addSpecialModel( new ModelResourceLocation( new ResourceLocation( ComputerCraft.MOD_ID, model ), "inventory" ) );
|
||||
}
|
||||
}
|
||||
|
||||
@ -148,4 +121,61 @@ public final class ClientRegistry
|
||||
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 );
|
||||
}
|
||||
}
|
||||
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -11,12 +11,19 @@ import dan200.computercraft.core.apis.http.options.AddressRule;
|
||||
import dan200.computercraft.core.apis.http.options.Options;
|
||||
import dan200.computercraft.shared.util.ThreadUtils;
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import io.netty.channel.ConnectTimeoutException;
|
||||
import io.netty.channel.EventLoopGroup;
|
||||
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.SslContextBuilder;
|
||||
import io.netty.handler.timeout.ReadTimeoutException;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
import javax.net.ssl.SSLException;
|
||||
import javax.net.ssl.SSLHandshakeException;
|
||||
import javax.net.ssl.TrustManagerFactory;
|
||||
import java.net.InetSocketAddress;
|
||||
import java.net.URI;
|
||||
@ -161,4 +168,29 @@ public final class NetworkUtils
|
||||
buffer.readBytes( 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";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -19,13 +19,10 @@ import io.netty.buffer.Unpooled;
|
||||
import io.netty.channel.ChannelFuture;
|
||||
import io.netty.channel.ChannelInitializer;
|
||||
import io.netty.channel.ChannelPipeline;
|
||||
import io.netty.channel.ConnectTimeoutException;
|
||||
import io.netty.channel.socket.SocketChannel;
|
||||
import io.netty.channel.socket.nio.NioSocketChannel;
|
||||
import io.netty.handler.codec.TooLongFrameException;
|
||||
import io.netty.handler.codec.http.*;
|
||||
import io.netty.handler.ssl.SslContext;
|
||||
import io.netty.handler.timeout.ReadTimeoutException;
|
||||
import io.netty.handler.timeout.ReadTimeoutHandler;
|
||||
|
||||
import java.net.InetSocketAddress;
|
||||
@ -190,7 +187,7 @@ public class HttpRequest extends Resource<HttpRequest>
|
||||
.remoteAddress( socketAddress )
|
||||
.connect()
|
||||
.addListener( c -> {
|
||||
if( !c.isSuccess() ) failure( c.cause() );
|
||||
if( !c.isSuccess() ) failure( NetworkUtils.toFriendlyError( c.cause() ) );
|
||||
} );
|
||||
|
||||
// Do an additional check for cancellation
|
||||
@ -202,7 +199,7 @@ public class HttpRequest extends Resource<HttpRequest>
|
||||
}
|
||||
catch( Exception e )
|
||||
{
|
||||
failure( "Could not connect" );
|
||||
failure( NetworkUtils.toFriendlyError( 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 );
|
||||
}
|
||||
|
||||
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 )
|
||||
{
|
||||
if( tryClose() ) environment.queueEvent( FAILURE_EVENT, address, message, object );
|
||||
|
@ -183,7 +183,7 @@ public final class HttpRequestHandler extends SimpleChannelInboundHandler<HttpOb
|
||||
public void exceptionCaught( ChannelHandlerContext ctx, Throwable cause )
|
||||
{
|
||||
if( ComputerCraft.logComputerErrors ) ComputerCraft.log.error( "Error handling HTTP response", cause );
|
||||
request.failure( cause );
|
||||
request.failure( NetworkUtils.toFriendlyError( cause ) );
|
||||
}
|
||||
|
||||
private void sendResponse()
|
||||
|
@ -166,7 +166,7 @@ public class Websocket extends Resource<Websocket>
|
||||
.remoteAddress( socketAddress )
|
||||
.connect()
|
||||
.addListener( c -> {
|
||||
if( !c.isSuccess() ) failure( c.cause().getMessage() );
|
||||
if( !c.isSuccess() ) failure( NetworkUtils.toFriendlyError( c.cause() ) );
|
||||
} );
|
||||
|
||||
// Do an additional check for cancellation
|
||||
@ -178,7 +178,7 @@ public class Websocket extends Resource<Websocket>
|
||||
}
|
||||
catch( Exception e )
|
||||
{
|
||||
failure( "Could not connect" );
|
||||
failure( NetworkUtils.toFriendlyError( e ) );
|
||||
if( ComputerCraft.logComputerErrors ) ComputerCraft.log.error( "Error in websocket", e );
|
||||
}
|
||||
}
|
||||
|
@ -5,17 +5,13 @@
|
||||
*/
|
||||
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.options.Options;
|
||||
import dan200.computercraft.core.tracking.TrackingField;
|
||||
import io.netty.channel.ChannelHandlerContext;
|
||||
import io.netty.channel.ConnectTimeoutException;
|
||||
import io.netty.channel.SimpleChannelInboundHandler;
|
||||
import io.netty.handler.codec.TooLongFrameException;
|
||||
import io.netty.handler.codec.http.FullHttpResponse;
|
||||
import io.netty.handler.codec.http.websocketx.*;
|
||||
import io.netty.handler.timeout.ReadTimeoutException;
|
||||
import io.netty.util.CharsetUtil;
|
||||
|
||||
import static dan200.computercraft.core.apis.http.websocket.Websocket.MESSAGE_EVENT;
|
||||
@ -97,24 +93,7 @@ public class WebsocketHandler extends SimpleChannelInboundHandler<Object>
|
||||
{
|
||||
ctx.close();
|
||||
|
||||
String message;
|
||||
if( cause instanceof WebSocketHandshakeException || cause instanceof HTTPRequestException )
|
||||
{
|
||||
message = cause.getMessage();
|
||||
}
|
||||
else if( cause instanceof TooLongFrameException )
|
||||
{
|
||||
message = "Message is too large";
|
||||
}
|
||||
else if( cause instanceof ReadTimeoutException || cause instanceof ConnectTimeoutException )
|
||||
{
|
||||
message = "Timed out";
|
||||
}
|
||||
else
|
||||
{
|
||||
message = "Could not connect";
|
||||
}
|
||||
|
||||
String message = NetworkUtils.toFriendlyError( cause );
|
||||
if( handshaker.isHandshakeComplete() )
|
||||
{
|
||||
websocket.close( -1, message );
|
||||
|
@ -5,7 +5,7 @@
|
||||
*/
|
||||
package dan200.computercraft.data;
|
||||
|
||||
import dan200.computercraft.shared.proxy.ComputerCraftProxyCommon;
|
||||
import dan200.computercraft.shared.Registry;
|
||||
import net.minecraft.data.DataGenerator;
|
||||
import net.minecraftforge.eventbus.api.SubscribeEvent;
|
||||
import net.minecraftforge.fml.common.Mod;
|
||||
@ -17,7 +17,7 @@ public class Generators
|
||||
@SubscribeEvent
|
||||
public static void gather( GatherDataEvent event )
|
||||
{
|
||||
ComputerCraftProxyCommon.registerLoot();
|
||||
Registry.registerLoot();
|
||||
|
||||
DataGenerator generator = event.getGenerator();
|
||||
generator.addProvider( new Recipes( generator ) );
|
||||
|
@ -10,7 +10,7 @@ import dan200.computercraft.shared.Registry;
|
||||
import dan200.computercraft.shared.data.BlockNamedEntityLootCondition;
|
||||
import dan200.computercraft.shared.data.HasComputerIdLootCondition;
|
||||
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.data.DataGenerator;
|
||||
import net.minecraft.loot.*;
|
||||
@ -46,7 +46,7 @@ public class LootTables extends LootTableProvider
|
||||
computerDrop( add, Registry.ModBlocks.TURTLE_NORMAL );
|
||||
computerDrop( add, Registry.ModBlocks.TURTLE_ADVANCED );
|
||||
|
||||
add.accept( ComputerCraftProxyCommon.ForgeHandlers.LOOT_TREASURE_DISK, LootTable
|
||||
add.accept( CommonHooks.LOOT_TREASURE_DISK, LootTable
|
||||
.lootTable()
|
||||
.setParamSet( LootParameterSets.ALL_PARAMS )
|
||||
.build() );
|
||||
|
138
src/main/java/dan200/computercraft/shared/CommonHooks.java
Normal file
138
src/main/java/dan200/computercraft/shared/CommonHooks.java
Normal 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() );
|
||||
}
|
||||
}
|
@ -7,8 +7,13 @@ package dan200.computercraft.shared;
|
||||
|
||||
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.shared.command.arguments.ArgumentSerializers;
|
||||
import dan200.computercraft.shared.common.ColourableRecipe;
|
||||
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.TileCommandComputer;
|
||||
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.items.ItemComputer;
|
||||
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.ItemPrintout;
|
||||
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.PrintoutRecipe;
|
||||
import dan200.computercraft.shared.network.NetworkHandler;
|
||||
import dan200.computercraft.shared.network.container.ComputerContainerData;
|
||||
import dan200.computercraft.shared.network.container.ContainerData;
|
||||
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.ContainerDiskDrive;
|
||||
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.wireless.BlockWirelessModem;
|
||||
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.TurtleUpgradeRecipe;
|
||||
import dan200.computercraft.shared.turtle.upgrades.*;
|
||||
import dan200.computercraft.shared.util.CreativeTabMain;
|
||||
import dan200.computercraft.shared.util.FixedPointTileEntityType;
|
||||
import dan200.computercraft.shared.util.ImpostorRecipe;
|
||||
import dan200.computercraft.shared.util.ImpostorShapelessRecipe;
|
||||
import dan200.computercraft.shared.util.*;
|
||||
import net.minecraft.block.AbstractBlock;
|
||||
import net.minecraft.block.Block;
|
||||
import net.minecraft.block.material.Material;
|
||||
import net.minecraft.entity.EntityClassification;
|
||||
import net.minecraft.entity.EntityType;
|
||||
import net.minecraft.inventory.container.ContainerType;
|
||||
import net.minecraft.item.BlockItem;
|
||||
import net.minecraft.item.Item;
|
||||
import net.minecraft.item.ItemGroup;
|
||||
import net.minecraft.item.Items;
|
||||
import net.minecraft.item.*;
|
||||
import net.minecraft.item.crafting.IRecipeSerializer;
|
||||
import net.minecraft.loot.LootConditionType;
|
||||
import net.minecraft.tileentity.TileEntity;
|
||||
import net.minecraft.tileentity.TileEntityType;
|
||||
import net.minecraft.util.ResourceLocation;
|
||||
import net.minecraftforge.common.capabilities.CapabilityManager;
|
||||
import net.minecraftforge.energy.CapabilityEnergy;
|
||||
import net.minecraftforge.event.RegistryEvent;
|
||||
import net.minecraftforge.eventbus.api.IEventBus;
|
||||
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.common.Mod;
|
||||
import net.minecraftforge.fml.event.lifecycle.FMLCommonSetupEvent;
|
||||
import net.minecraftforge.fml.javafmlmod.FMLJavaModLoadingContext;
|
||||
import net.minecraftforge.items.CapabilityItemHandler;
|
||||
import net.minecraftforge.registries.DeferredRegister;
|
||||
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()
|
||||
{
|
||||
IEventBus bus = FMLJavaModLoadingContext.get().getModEventBus();
|
||||
|
@ -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 );
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -21,6 +21,7 @@ import dan200.computercraft.shared.computer.core.ComputerFamily;
|
||||
import dan200.computercraft.shared.computer.core.ServerComputer;
|
||||
import dan200.computercraft.shared.computer.inventory.ContainerViewComputer;
|
||||
import dan200.computercraft.shared.network.container.ViewComputerContainerData;
|
||||
import dan200.computercraft.shared.util.IDAssigner;
|
||||
import net.minecraft.command.CommandSource;
|
||||
import net.minecraft.entity.Entity;
|
||||
import net.minecraft.entity.player.PlayerEntity;
|
||||
@ -38,6 +39,7 @@ import net.minecraft.world.World;
|
||||
import net.minecraft.world.server.ServerWorld;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
import java.io.File;
|
||||
import java.util.*;
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
@ -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
|
||||
private static TrackingContext getTimingContext( CommandSource source )
|
||||
{
|
||||
|
@ -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" ) ) ) );
|
||||
}
|
||||
}
|
@ -57,14 +57,15 @@ public enum UserLevel implements Predicate<CommandSource>
|
||||
{
|
||||
if( this == ANYONE ) return true;
|
||||
|
||||
// We *always* allow level 0 stuff, even if the
|
||||
MinecraftServer server = source.getServer();
|
||||
Entity sender = source.getEntity();
|
||||
|
||||
if( server.isSingleplayer() && sender instanceof PlayerEntity &&
|
||||
((PlayerEntity) sender).getGameProfile().getName().equalsIgnoreCase( server.getServerModName() ) )
|
||||
if( this == OWNER || this == OWNER_OP )
|
||||
{
|
||||
if( this == OWNER || this == OWNER_OP ) return true;
|
||||
MinecraftServer server = source.getServer();
|
||||
Entity sender = source.getEntity();
|
||||
if( server.isSingleplayer() && sender instanceof PlayerEntity &&
|
||||
((PlayerEntity) sender).getGameProfile().getName().equalsIgnoreCase( server.getServerModName() ) )
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return source.hasPermission( toLevel() );
|
||||
|
@ -68,19 +68,33 @@ public final class ChatHelpers
|
||||
: 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();
|
||||
|
||||
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 ) );
|
||||
|
||||
return component.setStyle( style );
|
||||
return component.copy().withStyle( style );
|
||||
}
|
||||
|
||||
public static IFormattableTextComponent header( String text )
|
||||
{
|
||||
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 );
|
||||
}
|
||||
}
|
||||
|
@ -57,7 +57,7 @@ public class TileCommandComputer extends TileComputer
|
||||
@Override
|
||||
public boolean acceptsSuccess()
|
||||
{
|
||||
return getLevel().getGameRules().getBoolean( GameRules.RULE_SENDCOMMANDFEEDBACK );
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -175,12 +175,12 @@ public abstract class TileComputerBase extends TileGeneric implements IComputerT
|
||||
label = computer.getLabel();
|
||||
on = computer.isOn();
|
||||
|
||||
if( computer.hasOutputChanged() ) updateOutput();
|
||||
|
||||
// Update the block state if needed. We don't fire a block update intentionally,
|
||||
// as this only really is needed on the client side.
|
||||
updateBlockState( computer.getState() );
|
||||
|
||||
// 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();
|
||||
}
|
||||
}
|
||||
@ -376,11 +376,8 @@ public abstract class TileComputerBase extends TileGeneric implements IComputerT
|
||||
fresh = true;
|
||||
changed = true;
|
||||
}
|
||||
if( changed )
|
||||
{
|
||||
updateBlock();
|
||||
updateInput();
|
||||
}
|
||||
|
||||
if( changed ) updateInput();
|
||||
return ComputerCraft.serverComputerRegistry.get( instanceID );
|
||||
}
|
||||
|
||||
|
@ -68,6 +68,8 @@ public class ItemTreasureDisk extends Item implements IMedia
|
||||
public IMount createDataMount( @Nonnull ItemStack stack, @Nonnull World world )
|
||||
{
|
||||
IMount rootTreasure = getTreasureMount();
|
||||
if( rootTreasure == null ) return null;
|
||||
|
||||
String subPath = getSubPath( stack );
|
||||
try
|
||||
{
|
||||
@ -121,7 +123,7 @@ public class ItemTreasureDisk extends Item implements IMedia
|
||||
private static String getTitle( @Nonnull ItemStack stack )
|
||||
{
|
||||
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
|
||||
|
@ -11,7 +11,7 @@ import dan200.computercraft.api.network.wired.IWiredElement;
|
||||
import dan200.computercraft.api.network.wired.IWiredNode;
|
||||
import dan200.computercraft.api.peripheral.IPeripheral;
|
||||
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.peripheral.modem.ModemState;
|
||||
import dan200.computercraft.shared.util.CapabilityUtil;
|
||||
@ -268,12 +268,12 @@ public class TileCable extends TileGeneric
|
||||
if( oldName != null )
|
||||
{
|
||||
player.displayClientMessage( new TranslationTextComponent( "chat.computercraft.wired_modem.peripheral_disconnected",
|
||||
CommandCopy.createCopyText( oldName ) ), false );
|
||||
ChatHelpers.copy( oldName ) ), false );
|
||||
}
|
||||
if( newName != null )
|
||||
{
|
||||
player.displayClientMessage( new TranslationTextComponent( "chat.computercraft.wired_modem.peripheral_connected",
|
||||
CommandCopy.createCopyText( newName ) ), false );
|
||||
ChatHelpers.copy( newName ) ), false );
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -10,7 +10,7 @@ import dan200.computercraft.api.ComputerCraftAPI;
|
||||
import dan200.computercraft.api.network.wired.IWiredElement;
|
||||
import dan200.computercraft.api.network.wired.IWiredNode;
|
||||
import dan200.computercraft.api.peripheral.IPeripheral;
|
||||
import dan200.computercraft.shared.command.CommandCopy;
|
||||
import dan200.computercraft.shared.command.text.ChatHelpers;
|
||||
import dan200.computercraft.shared.common.TileGeneric;
|
||||
import dan200.computercraft.shared.peripheral.modem.ModemState;
|
||||
import dan200.computercraft.shared.util.CapabilityUtil;
|
||||
@ -218,7 +218,7 @@ public class TileWiredModemFull extends TileGeneric
|
||||
for( int i = 0; i < names.size(); i++ )
|
||||
{
|
||||
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 );
|
||||
|
@ -149,7 +149,7 @@ public class TileMonitor extends TileGeneric
|
||||
@Override
|
||||
public void blockTick()
|
||||
{
|
||||
if ( needsUpdate )
|
||||
if( needsUpdate )
|
||||
{
|
||||
needsUpdate = false;
|
||||
updateNeighbors();
|
||||
@ -279,8 +279,6 @@ public class TileMonitor extends TileGeneric
|
||||
|
||||
int oldXIndex = xIndex;
|
||||
int oldYIndex = yIndex;
|
||||
int oldWidth = width;
|
||||
int oldHeight = height;
|
||||
|
||||
xIndex = nbt.getInt( NBT_X );
|
||||
yIndex = nbt.getInt( NBT_Y );
|
||||
@ -300,13 +298,6 @@ public class TileMonitor extends TileGeneric
|
||||
// If we're the origin terminal then create it.
|
||||
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 )
|
||||
|
@ -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() );
|
||||
}
|
||||
}
|
||||
}
|
@ -160,8 +160,8 @@ public class TurtleBrain implements ITurtleAccess
|
||||
overlay = nbt.contains( NBT_OVERLAY ) ? new ResourceLocation( nbt.getString( NBT_OVERLAY ) ) : null;
|
||||
|
||||
// Read upgrades
|
||||
setUpgrade( TurtleSide.LEFT, nbt.contains( NBT_LEFT_UPGRADE ) ? TurtleUpgrades.get( nbt.getString( NBT_LEFT_UPGRADE ) ) : null );
|
||||
setUpgrade( TurtleSide.RIGHT, nbt.contains( NBT_RIGHT_UPGRADE ) ? TurtleUpgrades.get( nbt.getString( NBT_RIGHT_UPGRADE ) ) : null );
|
||||
setUpgradeDirect( TurtleSide.LEFT, nbt.contains( NBT_LEFT_UPGRADE ) ? TurtleUpgrades.get( nbt.getString( NBT_LEFT_UPGRADE ) ) : null );
|
||||
setUpgradeDirect( TurtleSide.RIGHT, nbt.contains( NBT_RIGHT_UPGRADE ) ? TurtleUpgrades.get( nbt.getString( NBT_RIGHT_UPGRADE ) ) : null );
|
||||
|
||||
// NBT
|
||||
upgradeNBTData.clear();
|
||||
@ -618,16 +618,30 @@ public class TurtleBrain implements ITurtleAccess
|
||||
|
||||
@Override
|
||||
public void setUpgrade( @Nonnull TurtleSide side, ITurtleUpgrade upgrade )
|
||||
{
|
||||
if( !setUpgradeDirect( side, upgrade ) ) return;
|
||||
|
||||
// This is a separate function to avoid updating the block when reading the NBT. We don't need to do this as
|
||||
// either the block is newly placed (and so won't have changed) or is being updated with /data, which calls
|
||||
// updateBlock for us.
|
||||
if( owner.getLevel() != null )
|
||||
{
|
||||
owner.updateBlock();
|
||||
owner.updateInput();
|
||||
}
|
||||
}
|
||||
|
||||
private boolean setUpgradeDirect( @Nonnull TurtleSide side, ITurtleUpgrade upgrade )
|
||||
{
|
||||
// Remove old upgrade
|
||||
if( upgrades.containsKey( side ) )
|
||||
{
|
||||
if( upgrades.get( side ) == upgrade ) return;
|
||||
if( upgrades.get( side ) == upgrade ) return false;
|
||||
upgrades.remove( side );
|
||||
}
|
||||
else
|
||||
{
|
||||
if( upgrade == null ) return;
|
||||
if( upgrade == null ) return false;
|
||||
}
|
||||
|
||||
upgradeNBTData.remove( side );
|
||||
@ -639,8 +653,9 @@ public class TurtleBrain implements ITurtleAccess
|
||||
if( owner.getLevel() != null )
|
||||
{
|
||||
updatePeripherals( owner.createServerComputer() );
|
||||
owner.updateBlock();
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -62,7 +62,7 @@ public class TurtleDropCommand implements ITurtleCommand
|
||||
IItemHandler inventory = InventoryUtil.getInventory( world, newPosition, side );
|
||||
|
||||
// Fire the event, restoring the inventory and exiting if it is cancelled.
|
||||
TurtlePlayer player = TurtlePlaceCommand.createPlayer( turtle, oldPosition, direction );
|
||||
TurtlePlayer player = TurtlePlayer.getWithPosition( turtle, oldPosition, direction );
|
||||
TurtleInventoryEvent.Drop event = new TurtleInventoryEvent.Drop( turtle, player, world, newPosition, inventory, stack );
|
||||
if( MinecraftForge.EVENT_BUS.post( event ) )
|
||||
{
|
||||
|
@ -50,7 +50,7 @@ public class TurtleInspectCommand implements ITurtleCommand
|
||||
Map<String, Object> table = BlockData.fill( new HashMap<>(), state );
|
||||
|
||||
// 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 );
|
||||
if( MinecraftForge.EVENT_BUS.post( event ) ) return TurtleCommandResult.failure( event.getFailureMessage() );
|
||||
|
||||
|
@ -47,7 +47,7 @@ public class TurtleMoveCommand implements ITurtleCommand
|
||||
BlockPos oldPosition = turtle.getPosition();
|
||||
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 );
|
||||
if( !canEnterResult.isSuccess() )
|
||||
{
|
||||
|
@ -12,7 +12,6 @@ import dan200.computercraft.api.turtle.TurtleAnimation;
|
||||
import dan200.computercraft.api.turtle.TurtleCommandResult;
|
||||
import dan200.computercraft.api.turtle.event.TurtleBlockEvent;
|
||||
import dan200.computercraft.shared.TurtlePermissions;
|
||||
import dan200.computercraft.shared.util.DirectionUtil;
|
||||
import dan200.computercraft.shared.util.DropConsumer;
|
||||
import dan200.computercraft.shared.util.InventoryUtil;
|
||||
import dan200.computercraft.shared.util.WorldUtil;
|
||||
@ -20,6 +19,7 @@ import net.minecraft.block.BlockState;
|
||||
import net.minecraft.entity.Entity;
|
||||
import net.minecraft.entity.LivingEntity;
|
||||
import net.minecraft.item.*;
|
||||
import net.minecraft.network.play.client.CUseEntityPacket;
|
||||
import net.minecraft.tileentity.SignTileEntity;
|
||||
import net.minecraft.tileentity.TileEntity;
|
||||
import net.minecraft.util.ActionResult;
|
||||
@ -34,11 +34,13 @@ import net.minecraft.world.World;
|
||||
import net.minecraftforge.common.ForgeHooks;
|
||||
import net.minecraftforge.common.MinecraftForge;
|
||||
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 javax.annotation.Nonnull;
|
||||
import java.util.List;
|
||||
|
||||
import static net.minecraftforge.eventbus.api.Event.Result;
|
||||
|
||||
public class TurtlePlaceCommand implements ITurtleCommand
|
||||
{
|
||||
@ -57,10 +59,7 @@ public class TurtlePlaceCommand implements ITurtleCommand
|
||||
{
|
||||
// Get thing to place
|
||||
ItemStack stack = turtle.getInventory().getItem( turtle.getSelectedSlot() );
|
||||
if( stack.isEmpty() )
|
||||
{
|
||||
return TurtleCommandResult.failure( "No items to place" );
|
||||
}
|
||||
if( stack.isEmpty() ) return TurtleCommandResult.failure( "No items to place" );
|
||||
|
||||
// Remember old block
|
||||
Direction direction = this.direction.toWorldDir( turtle );
|
||||
@ -68,144 +67,63 @@ public class TurtlePlaceCommand implements ITurtleCommand
|
||||
|
||||
// Create a fake player, and orient it appropriately
|
||||
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 );
|
||||
if( MinecraftForge.EVENT_BUS.post( place ) )
|
||||
{
|
||||
return TurtleCommandResult.failure( place.getFailureMessage() );
|
||||
}
|
||||
if( MinecraftForge.EVENT_BUS.post( place ) ) return TurtleCommandResult.failure( place.getFailureMessage() );
|
||||
|
||||
// Do the deploying
|
||||
String[] errorMessage = new String[1];
|
||||
ItemStack remainder = deploy( stack, turtle, turtlePlayer, direction, extraArguments, errorMessage );
|
||||
if( remainder != stack )
|
||||
turtlePlayer.loadInventory( turtle );
|
||||
ErrorMessage message = new ErrorMessage();
|
||||
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
|
||||
turtle.playAnimation( TurtleAnimation.WAIT );
|
||||
return TurtleCommandResult.success();
|
||||
}
|
||||
else if( message.message != null )
|
||||
{
|
||||
return TurtleCommandResult.failure( message.message );
|
||||
}
|
||||
else
|
||||
{
|
||||
if( errorMessage[0] != null )
|
||||
{
|
||||
return TurtleCommandResult.failure( errorMessage[0] );
|
||||
}
|
||||
else if( stack.getItem() instanceof BlockItem )
|
||||
{
|
||||
return TurtleCommandResult.failure( "Cannot place block here" );
|
||||
}
|
||||
else
|
||||
{
|
||||
return TurtleCommandResult.failure( "Cannot place item here" );
|
||||
}
|
||||
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
|
||||
BlockPos playerPosition = turtle.getPosition().relative( direction );
|
||||
TurtlePlayer turtlePlayer = createPlayer( turtle, playerPosition, direction );
|
||||
|
||||
return deploy( stack, turtle, turtlePlayer, direction, extraArguments, outErrorMessage );
|
||||
TurtlePlayer turtlePlayer = TurtlePlayer.getWithPosition( turtle, playerPosition, direction );
|
||||
turtlePlayer.loadInventory( stack );
|
||||
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
|
||||
ItemStack remainder = deployOnEntity( stack, turtle, turtlePlayer, direction, extraArguments, outErrorMessage );
|
||||
if( remainder != stack )
|
||||
{
|
||||
return remainder;
|
||||
}
|
||||
if( deployOnEntity( stack, turtle, turtlePlayer ) ) return true;
|
||||
|
||||
// Deploy on the block immediately in front
|
||||
BlockPos position = turtle.getPosition();
|
||||
BlockPos newPosition = position.relative( direction );
|
||||
remainder = deployOnBlock( stack, turtle, turtlePlayer, newPosition, direction.getOpposite(), extraArguments, true, outErrorMessage );
|
||||
if( remainder != stack )
|
||||
{
|
||||
return remainder;
|
||||
}
|
||||
|
||||
// Deploy on the block one block away
|
||||
remainder = deployOnBlock( stack, turtle, turtlePlayer, newPosition.relative( direction ), direction.getOpposite(), extraArguments, false, outErrorMessage );
|
||||
if( remainder != stack )
|
||||
{
|
||||
return remainder;
|
||||
}
|
||||
|
||||
if( direction.getAxis() != Direction.Axis.Y )
|
||||
{
|
||||
// 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
|
||||
|| deployOnBlock( stack, turtle, turtlePlayer, newPosition.relative( direction ), direction.getOpposite(), extraArguments, false, outErrorMessage )
|
||||
// Deploy down on the block in front
|
||||
remainder = deployOnBlock( stack, turtle, turtlePlayer, newPosition.below(), Direction.UP, extraArguments, false, outErrorMessage );
|
||||
if( remainder != stack )
|
||||
{
|
||||
return remainder;
|
||||
}
|
||||
}
|
||||
|
||||
// Deploy back onto the turtle
|
||||
remainder = deployOnBlock( stack, turtle, turtlePlayer, position, direction, extraArguments, false, outErrorMessage );
|
||||
if( remainder != stack )
|
||||
{
|
||||
return remainder;
|
||||
}
|
||||
|
||||
// If nothing worked, return the original stack unchanged
|
||||
return stack;
|
||||
|| (direction.getAxis() != Direction.Axis.Y && deployOnBlock( stack, turtle, turtlePlayer, newPosition.below(), Direction.UP, extraArguments, false, outErrorMessage ))
|
||||
// Deploy back onto the turtle
|
||||
|| deployOnBlock( stack, turtle, turtlePlayer, position, direction, extraArguments, false, outErrorMessage );
|
||||
}
|
||||
|
||||
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 )
|
||||
private static boolean deployOnEntity( @Nonnull ItemStack stack, final ITurtleAccess turtle, TurtlePlayer turtlePlayer )
|
||||
{
|
||||
// See if there is an entity present
|
||||
final World world = turtle.getWorld();
|
||||
@ -213,81 +131,57 @@ public class TurtlePlaceCommand implements ITurtleCommand
|
||||
Vector3d turtlePos = turtlePlayer.position();
|
||||
Vector3d rayDir = turtlePlayer.getViewVector( 1.0f );
|
||||
Pair<Entity, Vector3d> hit = WorldUtil.rayTraceEntities( world, turtlePos, rayDir, 1.5 );
|
||||
if( hit == null )
|
||||
{
|
||||
return stack;
|
||||
}
|
||||
|
||||
// Load up the turtle's inventory
|
||||
ItemStack stackCopy = stack.copy();
|
||||
turtlePlayer.loadInventory( stackCopy );
|
||||
if( hit == null ) return false;
|
||||
|
||||
// Start claiming entity drops
|
||||
Entity hitEntity = hit.getKey();
|
||||
Vector3d hitPos = hit.getValue();
|
||||
DropConsumer.set(
|
||||
hitEntity,
|
||||
drop -> InventoryUtil.storeItems( drop, turtle.getItemHandler(), turtle.getSelectedSlot() )
|
||||
);
|
||||
|
||||
// Place on the entity
|
||||
boolean placed = false;
|
||||
ActionResultType cancelResult = ForgeHooks.onInteractEntityAt( turtlePlayer, hitEntity, hitPos, Hand.MAIN_HAND );
|
||||
if( cancelResult == null )
|
||||
{
|
||||
cancelResult = hitEntity.interactAt( turtlePlayer, hitPos, Hand.MAIN_HAND );
|
||||
}
|
||||
IItemHandler itemHandler = new InvWrapper( turtlePlayer.inventory );
|
||||
DropConsumer.set( hitEntity, drop -> InventoryUtil.storeItems( drop, itemHandler, 1 ) );
|
||||
|
||||
if( cancelResult.consumesAction() )
|
||||
{
|
||||
placed = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
// See EntityPlayer.interactOn
|
||||
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 );
|
||||
}
|
||||
}
|
||||
}
|
||||
boolean placed = doDeployOnEntity( stack, turtlePlayer, hitEntity, hitPos );
|
||||
|
||||
// Stop claiming drops
|
||||
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
|
||||
ItemStack remainder = turtlePlayer.unloadInventory( turtle );
|
||||
if( !placed && ItemStack.matches( stack, remainder ) )
|
||||
{
|
||||
return stack;
|
||||
}
|
||||
else if( !remainder.isEmpty() )
|
||||
{
|
||||
return remainder;
|
||||
}
|
||||
else
|
||||
{
|
||||
return ItemStack.EMPTY;
|
||||
}
|
||||
DropConsumer.clearAndDrop( world, position, turtle.getDirection().getOpposite() );
|
||||
return placed;
|
||||
}
|
||||
|
||||
private static boolean canDeployOnBlock( @Nonnull BlockItemUseContext context, ITurtleAccess turtle, TurtlePlayer player, BlockPos position, Direction side, boolean allowReplaceable, String[] outErrorMessage )
|
||||
/**
|
||||
* 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 )
|
||||
{
|
||||
// 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.
|
||||
|
||||
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 )
|
||||
{
|
||||
return stack.interactLivingEntity( turtlePlayer, (LivingEntity) hitEntity, Hand.MAIN_HAND ).consumesAction();
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private static boolean canDeployOnBlock(
|
||||
@Nonnull BlockItemUseContext context, ITurtleAccess turtle, TurtlePlayer player, BlockPos position,
|
||||
Direction side, boolean allowReplaceable, ErrorMessage outErrorMessage
|
||||
)
|
||||
{
|
||||
World world = turtle.getWorld();
|
||||
if( !World.isInWorldBounds( position ) || world.isEmptyBlock( position ) ||
|
||||
@ -309,7 +203,7 @@ public class TurtlePlaceCommand implements ITurtleCommand
|
||||
: TurtlePermissions.isBlockEditable( world, position.relative( side ), player );
|
||||
if( !editable )
|
||||
{
|
||||
if( outErrorMessage != null ) outErrorMessage[0] = "Cannot place in protected area";
|
||||
if( outErrorMessage != null ) outErrorMessage.message = "Cannot place in protected area";
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@ -317,129 +211,124 @@ public class TurtlePlaceCommand implements ITurtleCommand
|
||||
return true;
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
private static ItemStack deployOnBlock( @Nonnull ItemStack stack, ITurtleAccess turtle, TurtlePlayer turtlePlayer, BlockPos position, Direction side, Object[] extraArguments, boolean allowReplace, String[] outErrorMessage )
|
||||
private static boolean deployOnBlock(
|
||||
@Nonnull ItemStack stack, ITurtleAccess turtle, TurtlePlayer turtlePlayer, BlockPos position, Direction side,
|
||||
Object[] extraArguments, boolean allowReplace, ErrorMessage outErrorMessage
|
||||
)
|
||||
{
|
||||
// Re-orient the fake player
|
||||
Direction playerDir = side.getOpposite();
|
||||
BlockPos playerPosition = position.relative( side );
|
||||
orientPlayer( turtle, turtlePlayer, playerPosition, playerDir );
|
||||
|
||||
ItemStack stackCopy = stack.copy();
|
||||
turtlePlayer.loadInventory( stackCopy );
|
||||
turtlePlayer.setPosition( turtle, playerPosition, playerDir );
|
||||
|
||||
// Calculate where the turtle would hit the block
|
||||
float hitX = 0.5f + side.getStepX() * 0.5f;
|
||||
float hitY = 0.5f + side.getStepY() * 0.5f;
|
||||
float hitZ = 0.5f + side.getStepZ() * 0.5f;
|
||||
if( Math.abs( hitY - 0.5f ) < 0.01f )
|
||||
{
|
||||
hitY = 0.45f;
|
||||
}
|
||||
if( Math.abs( hitY - 0.5f ) < 0.01f ) hitY = 0.45f;
|
||||
|
||||
// Check if there's something suitable to place onto
|
||||
BlockRayTraceResult hit = new BlockRayTraceResult( new Vector3d( hitX, hitY, hitZ ), side, position, false );
|
||||
ItemUseContext context = new ItemUseContext( turtlePlayer, Hand.MAIN_HAND, hit );
|
||||
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();
|
||||
|
||||
// Do the deploying (put everything in the players inventory)
|
||||
boolean placed = false;
|
||||
TileEntity existingTile = turtle.getWorld().getBlockEntity( position );
|
||||
|
||||
// See PlayerInteractionManager.processRightClickBlock
|
||||
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() );
|
||||
}
|
||||
}
|
||||
}
|
||||
boolean placed = doDeployOnBlock( stack, turtlePlayer, position, context, hit ).consumesAction();
|
||||
|
||||
// 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();
|
||||
TileEntity tile = world.getBlockEntity( position );
|
||||
if( tile == null || tile == existingTile )
|
||||
{
|
||||
World world = turtle.getWorld();
|
||||
TileEntity tile = world.getBlockEntity( position );
|
||||
if( tile == null || tile == existingTile )
|
||||
{
|
||||
tile = world.getBlockEntity( position.relative( side ) );
|
||||
}
|
||||
if( tile instanceof SignTileEntity )
|
||||
{
|
||||
SignTileEntity signTile = (SignTileEntity) tile;
|
||||
String s = (String) extraArguments[0];
|
||||
String[] split = s.split( "\n" );
|
||||
int firstLine = split.length <= 2 ? 1 : 0;
|
||||
for( int i = 0; i < 4; i++ )
|
||||
{
|
||||
if( i >= firstLine && i < firstLine + split.length )
|
||||
{
|
||||
if( split[i - firstLine].length() > 15 )
|
||||
{
|
||||
signTile.setMessage( i, new StringTextComponent( split[i - firstLine].substring( 0, 15 ) ) );
|
||||
}
|
||||
else
|
||||
{
|
||||
signTile.setMessage( i, new StringTextComponent( split[i - firstLine] ) );
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
signTile.setMessage( i, new StringTextComponent( "" ) );
|
||||
}
|
||||
}
|
||||
signTile.setChanged();
|
||||
world.sendBlockUpdated( tile.getBlockPos(), tile.getBlockState(), tile.getBlockState(), 3 );
|
||||
}
|
||||
tile = world.getBlockEntity( position.relative( side ) );
|
||||
}
|
||||
|
||||
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();
|
||||
}
|
||||
}
|
||||
|
||||
// Put everything we collected into the turtles inventory, then return
|
||||
ItemStack remainder = turtlePlayer.unloadInventory( turtle );
|
||||
if( !placed && ItemStack.matches( stack, remainder ) )
|
||||
return ActionResultType.PASS;
|
||||
}
|
||||
|
||||
private static void setSignText( World world, TileEntity tile, String message )
|
||||
{
|
||||
SignTileEntity signTile = (SignTileEntity) tile;
|
||||
String[] split = message.split( "\n" );
|
||||
int firstLine = split.length <= 2 ? 1 : 0;
|
||||
for( int i = 0; i < 4; i++ )
|
||||
{
|
||||
return stack;
|
||||
}
|
||||
else if( !remainder.isEmpty() )
|
||||
{
|
||||
return remainder;
|
||||
}
|
||||
else
|
||||
{
|
||||
return ItemStack.EMPTY;
|
||||
if( i >= firstLine && i < firstLine + split.length )
|
||||
{
|
||||
String line = split[i - firstLine];
|
||||
signTile.setMessage( i, line.length() > 15
|
||||
? new StringTextComponent( line.substring( 0, 15 ) )
|
||||
: new StringTextComponent( line )
|
||||
);
|
||||
}
|
||||
else
|
||||
{
|
||||
signTile.setMessage( i, new StringTextComponent( "" ) );
|
||||
}
|
||||
}
|
||||
signTile.setChanged();
|
||||
world.sendBlockUpdated( tile.getBlockPos(), tile.getBlockState(), tile.getBlockState(), 3 );
|
||||
}
|
||||
|
||||
private static class ErrorMessage
|
||||
{
|
||||
String message;
|
||||
}
|
||||
}
|
||||
|
@ -9,6 +9,7 @@ import com.mojang.authlib.GameProfile;
|
||||
import dan200.computercraft.ComputerCraft;
|
||||
import dan200.computercraft.api.turtle.ITurtleAccess;
|
||||
import dan200.computercraft.shared.Registry;
|
||||
import dan200.computercraft.shared.util.DirectionUtil;
|
||||
import dan200.computercraft.shared.util.FakeNetHandler;
|
||||
import dan200.computercraft.shared.util.InventoryUtil;
|
||||
import dan200.computercraft.shared.util.WorldUtil;
|
||||
@ -73,23 +74,6 @@ public final class TurtlePlayer extends FakePlayer
|
||||
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 )
|
||||
{
|
||||
if( !(access instanceof TurtleBrain) ) return create( access );
|
||||
@ -109,37 +93,114 @@ public final class TurtlePlayer extends FakePlayer
|
||||
return player;
|
||||
}
|
||||
|
||||
public void loadInventory( @Nonnull ItemStack currentStack )
|
||||
public static TurtlePlayer getWithPosition( ITurtleAccess turtle, BlockPos position, Direction direction )
|
||||
{
|
||||
// Load up the fake inventory
|
||||
inventory.selected = 0;
|
||||
inventory.setItem( 0, currentStack );
|
||||
TurtlePlayer turtlePlayer = get( turtle );
|
||||
turtlePlayer.setPosition( turtle, position, direction );
|
||||
return turtlePlayer;
|
||||
}
|
||||
|
||||
public ItemStack unloadInventory( ITurtleAccess turtle )
|
||||
private void setState( ITurtleAccess turtle )
|
||||
{
|
||||
// Get the item we placed with
|
||||
ItemStack results = inventory.getItem( 0 );
|
||||
inventory.setItem( 0, ItemStack.EMPTY );
|
||||
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 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
|
||||
BlockPos dropPosition = turtle.getPosition();
|
||||
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 );
|
||||
if( !stack.isEmpty() )
|
||||
ItemStack remainder = InventoryUtil.storeItems( inventory.getItem( i ), turtle.getItemHandler(), turtle.getSelectedSlot() );
|
||||
if( !remainder.isEmpty() )
|
||||
{
|
||||
ItemStack remainder = InventoryUtil.storeItems( stack, turtle.getItemHandler(), turtle.getSelectedSlot() );
|
||||
if( !remainder.isEmpty() )
|
||||
{
|
||||
WorldUtil.dropItemStack( remainder, turtle.getWorld(), dropPosition, dropDirection );
|
||||
}
|
||||
inventory.setItem( i, ItemStack.EMPTY );
|
||||
WorldUtil.dropItemStack( remainder, turtle.getWorld(), dropPosition, dropDirection );
|
||||
}
|
||||
}
|
||||
|
||||
inventory.setChanged();
|
||||
return results;
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
|
@ -58,7 +58,7 @@ public class TurtleSuckCommand implements ITurtleCommand
|
||||
IItemHandler inventory = InventoryUtil.getInventory( world, blockPosition, side );
|
||||
|
||||
// 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 );
|
||||
if( MinecraftForge.EVENT_BUS.post( event ) )
|
||||
{
|
||||
|
@ -13,14 +13,21 @@ import dan200.computercraft.shared.common.IColouredItem;
|
||||
import dan200.computercraft.shared.computer.core.ComputerFamily;
|
||||
import dan200.computercraft.shared.computer.items.ItemComputerBase;
|
||||
import dan200.computercraft.shared.turtle.blocks.BlockTurtle;
|
||||
import net.minecraft.block.BlockState;
|
||||
import net.minecraft.block.Blocks;
|
||||
import net.minecraft.block.CauldronBlock;
|
||||
import net.minecraft.item.ItemGroup;
|
||||
import net.minecraft.item.ItemStack;
|
||||
import net.minecraft.item.ItemUseContext;
|
||||
import net.minecraft.nbt.CompoundNBT;
|
||||
import net.minecraft.util.ActionResultType;
|
||||
import net.minecraft.util.NonNullList;
|
||||
import net.minecraft.util.ResourceLocation;
|
||||
import net.minecraft.util.math.BlockPos;
|
||||
import net.minecraft.util.text.ITextComponent;
|
||||
import net.minecraft.util.text.StringTextComponent;
|
||||
import net.minecraft.util.text.TranslationTextComponent;
|
||||
import net.minecraft.world.World;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
import javax.annotation.Nullable;
|
||||
@ -161,4 +168,27 @@ public class ItemTurtle extends ItemComputerBase implements ITurtleItem
|
||||
CompoundNBT tag = stack.getTag();
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
@ -59,9 +59,7 @@ public class TurtleHoe extends TurtleTool
|
||||
{
|
||||
if( verb == TurtleVerb.DIG )
|
||||
{
|
||||
ItemStack hoe = item.copy();
|
||||
ItemStack remainder = TurtlePlaceCommand.deploy( hoe, turtle, direction, null, null );
|
||||
if( remainder != hoe )
|
||||
if( TurtlePlaceCommand.deployCopiedItem( item.copy(), turtle, direction, null, null ) )
|
||||
{
|
||||
return TurtleCommandResult.success();
|
||||
}
|
||||
|
@ -63,9 +63,7 @@ public class TurtleShovel extends TurtleTool
|
||||
{
|
||||
if( verb == TurtleVerb.DIG )
|
||||
{
|
||||
ItemStack shovel = item.copy();
|
||||
ItemStack remainder = TurtlePlaceCommand.deploy( shovel, turtle, direction, null, null );
|
||||
if( remainder != shovel )
|
||||
if( TurtlePlaceCommand.deployCopiedItem( item.copy(), turtle, direction, null, null ) )
|
||||
{
|
||||
return TurtleCommandResult.success();
|
||||
}
|
||||
|
@ -12,7 +12,6 @@ import dan200.computercraft.api.turtle.event.TurtleAttackEvent;
|
||||
import dan200.computercraft.api.turtle.event.TurtleBlockEvent;
|
||||
import dan200.computercraft.shared.TurtlePermissions;
|
||||
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.util.DropConsumer;
|
||||
import dan200.computercraft.shared.util.InventoryUtil;
|
||||
@ -45,7 +44,6 @@ import net.minecraftforge.event.world.BlockEvent;
|
||||
import org.apache.commons.lang3.tuple.Pair;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
import java.util.List;
|
||||
import java.util.function.Function;
|
||||
|
||||
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 );
|
||||
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
|
||||
Vector3d turtlePos = turtlePlayer.position();
|
||||
@ -204,7 +202,7 @@ public class TurtleTool extends AbstractTurtleUpgrade
|
||||
// Put everything we collected into the turtles inventory, then return
|
||||
if( attacked )
|
||||
{
|
||||
turtlePlayer.unloadInventory( turtle );
|
||||
turtlePlayer.inventory.clearContent();
|
||||
return TurtleCommandResult.success();
|
||||
}
|
||||
}
|
||||
@ -229,7 +227,7 @@ public class TurtleTool extends AbstractTurtleUpgrade
|
||||
BlockState state = world.getBlockState( blockPosition );
|
||||
FluidState fluidState = world.getFluidState( blockPosition );
|
||||
|
||||
TurtlePlayer turtlePlayer = TurtlePlaceCommand.createPlayer( turtle, turtlePosition, direction );
|
||||
TurtlePlayer turtlePlayer = TurtlePlayer.getWithPosition( turtle, turtlePosition, direction );
|
||||
turtlePlayer.loadInventory( item.copy() );
|
||||
|
||||
if( ComputerCraft.turtlesObeyBlockProtection )
|
||||
@ -293,10 +291,6 @@ public class TurtleTool extends AbstractTurtleUpgrade
|
||||
private static void stopConsuming( TileEntity tile, ITurtleAccess turtle )
|
||||
{
|
||||
Direction direction = tile.isRemoved() ? null : turtle.getDirection().getOpposite();
|
||||
List<ItemStack> extra = DropConsumer.clear();
|
||||
for( ItemStack remainder : extra )
|
||||
{
|
||||
WorldUtil.dropItemStack( remainder, turtle.getWorld(), turtle.getPosition(), direction );
|
||||
}
|
||||
DropConsumer.clearAndDrop( turtle.getWorld(), turtle.getPosition(), direction );
|
||||
}
|
||||
}
|
||||
|
@ -9,6 +9,7 @@ import dan200.computercraft.ComputerCraft;
|
||||
import net.minecraft.entity.Entity;
|
||||
import net.minecraft.entity.item.ItemEntity;
|
||||
import net.minecraft.item.ItemStack;
|
||||
import net.minecraft.util.Direction;
|
||||
import net.minecraft.util.math.AxisAlignedBB;
|
||||
import net.minecraft.util.math.BlockPos;
|
||||
import net.minecraft.world.World;
|
||||
@ -66,6 +67,12 @@ public final class DropConsumer
|
||||
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 )
|
||||
{
|
||||
ItemStack remaining = dropConsumer.apply( stack );
|
||||
|
@ -34,8 +34,8 @@
|
||||
"upgrade.minecraft.diamond_axe.adjective": "Holzfällen",
|
||||
"upgrade.minecraft.diamond_hoe.adjective": "Ackerbau",
|
||||
"upgrade.minecraft.crafting_table.adjective": "Handwerk",
|
||||
"upgrade.computercraft.wireless_modem_normal.adjective": "Ender",
|
||||
"upgrade.computercraft.wireless_modem_advanced.adjective": "Kabellos",
|
||||
"upgrade.computercraft.wireless_modem_normal.adjective": "Kabellos",
|
||||
"upgrade.computercraft.wireless_modem_advanced.adjective": "Ender",
|
||||
"upgrade.computercraft.speaker.adjective": "Laut",
|
||||
"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",
|
||||
|
@ -48,6 +48,7 @@
|
||||
"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.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.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",
|
||||
|
@ -17,7 +17,7 @@
|
||||
"block.computercraft.turtle_normal.upgraded_twice": "Tortue %s et %s",
|
||||
"block.computercraft.turtle_advanced": "Tortue 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.treasure_disk": "Disquette",
|
||||
"item.computercraft.printed_page": "Page imprimée",
|
||||
@ -26,14 +26,14 @@
|
||||
"item.computercraft.pocket_computer_normal": "Ordinateur de poche",
|
||||
"item.computercraft.pocket_computer_normal.upgraded": "Ordinateur de poche %s",
|
||||
"item.computercraft.pocket_computer_advanced": "Ordinateur de poche avancé",
|
||||
"item.computercraft.pocket_computer_advanced.upgraded": "Ordinateur de poche %s avancé",
|
||||
"upgrade.minecraft.diamond_sword.adjective": "combattante",
|
||||
"upgrade.minecraft.diamond_shovel.adjective": "excavatrice",
|
||||
"upgrade.minecraft.diamond_pickaxe.adjective": "minière",
|
||||
"upgrade.minecraft.diamond_axe.adjective": "forestière",
|
||||
"upgrade.minecraft.diamond_hoe.adjective": "agricole",
|
||||
"upgrade.minecraft.crafting_table.adjective": "ouvrière",
|
||||
"upgrade.computercraft.wireless_modem_normal.adjective": "sans fil",
|
||||
"item.computercraft.pocket_computer_advanced.upgraded": "Ordinateur de poche avancé %s",
|
||||
"upgrade.minecraft.diamond_sword.adjective": "De Combat",
|
||||
"upgrade.minecraft.diamond_shovel.adjective": "Excavatrice",
|
||||
"upgrade.minecraft.diamond_pickaxe.adjective": "Mineuse",
|
||||
"upgrade.minecraft.diamond_axe.adjective": "Bûcheronne",
|
||||
"upgrade.minecraft.diamond_hoe.adjective": "Fermière",
|
||||
"upgrade.minecraft.crafting_table.adjective": "Ouvrière",
|
||||
"upgrade.computercraft.wireless_modem_normal.adjective": "Sans Fil",
|
||||
"upgrade.computercraft.wireless_modem_advanced.adjective": "de l'End",
|
||||
"upgrade.computercraft.speaker.adjective": "Bruyante",
|
||||
"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.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.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.exception": "Exception non gérée (%s)",
|
||||
"gui.computercraft.tooltip.disk_id": "ID de disque : %s",
|
||||
|
@ -262,41 +262,48 @@ local g_tLuaKeywords = {
|
||||
["while"] = true,
|
||||
}
|
||||
|
||||
local function serializeImpl(t, tTracking, sIndent)
|
||||
local function serialize_impl(t, tracking, indent, opts)
|
||||
local sType = type(t)
|
||||
if sType == "table" then
|
||||
if tTracking[t] ~= nil then
|
||||
if tracking[t] ~= nil then
|
||||
error("Cannot serialize table with recursive entries", 0)
|
||||
end
|
||||
tTracking[t] = true
|
||||
tracking[t] = true
|
||||
|
||||
local result
|
||||
if next(t) == nil then
|
||||
-- Empty tables are simple
|
||||
return "{}"
|
||||
result = "{}"
|
||||
else
|
||||
-- Other tables take more work
|
||||
local sResult = "{\n"
|
||||
local sSubIndent = sIndent .. " "
|
||||
local tSeen = {}
|
||||
local open, sub_indent, open_key, close_key, equal, comma = "{\n", indent .. " ", "[ ", " ] = ", " = ", ",\n"
|
||||
if opts.compact then
|
||||
open, sub_indent, open_key, close_key, equal, comma = "{", "", "[", "]=", "=", ","
|
||||
end
|
||||
|
||||
result = open
|
||||
local seen_keys = {}
|
||||
for k, v in ipairs(t) do
|
||||
tSeen[k] = true
|
||||
sResult = sResult .. sSubIndent .. serializeImpl(v, tTracking, sSubIndent) .. ",\n"
|
||||
seen_keys[k] = true
|
||||
result = result .. sub_indent .. serialize_impl(v, tracking, sub_indent, opts) .. comma
|
||||
end
|
||||
for k, v in pairs(t) do
|
||||
if not tSeen[k] then
|
||||
if not seen_keys[k] then
|
||||
local sEntry
|
||||
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
|
||||
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
|
||||
sResult = sResult .. sSubIndent .. sEntry
|
||||
result = result .. sub_indent .. sEntry
|
||||
end
|
||||
end
|
||||
sResult = sResult .. sIndent .. "}"
|
||||
return sResult
|
||||
result = result .. indent .. "}"
|
||||
end
|
||||
|
||||
if opts.allow_repetitions then tracking[t] = nil end
|
||||
return result
|
||||
|
||||
elseif sType == "string" then
|
||||
return string.format("%q", t)
|
||||
|
||||
@ -645,17 +652,43 @@ do
|
||||
end
|
||||
end
|
||||
|
||||
--- Convert a Lua object into a textual representation, suitable for
|
||||
-- saving in a file or pretty-printing.
|
||||
--
|
||||
-- @param t The object to serialise
|
||||
-- @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.
|
||||
function serialize(t)
|
||||
--[[- Convert a Lua object into a textual representation, suitable for
|
||||
saving in a file or pretty-printing.
|
||||
|
||||
@param t The object to serialise
|
||||
@tparam { compact? = boolean, allow_repetitions? = boolean } opts Options for serialisation.
|
||||
- `compact`: Do not emit indentation and other whitespace between terms.
|
||||
- `allow_repetitions`: Relax the check for recursive tables, allowing them to appear multiple
|
||||
times (as long as tables do not appear inside themselves).
|
||||
|
||||
@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 = {}
|
||||
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
|
||||
|
||||
serialise = serialize -- GB version
|
||||
|
@ -151,6 +151,15 @@ local vector = {
|
||||
tostring = function(self)
|
||||
return self.x .. "," .. self.y .. "," .. self.z
|
||||
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 = {
|
||||
@ -161,6 +170,7 @@ local vmetatable = {
|
||||
__div = vector.div,
|
||||
__unm = vector.unm,
|
||||
__tostring = vector.tostring,
|
||||
__eq = vector.equals,
|
||||
}
|
||||
|
||||
--- Construct a new @{Vector} with the given coordinates.
|
||||
|
@ -45,17 +45,19 @@ end
|
||||
-- @type Doc
|
||||
local Doc = { }
|
||||
|
||||
local function mk_doc(tbl) return setmetatable(tbl, Doc) end
|
||||
|
||||
--- An empty document.
|
||||
local empty = setmetatable({ tag = "nil" }, Doc)
|
||||
local empty = mk_doc({ tag = "nil" })
|
||||
|
||||
--- 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}.
|
||||
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}.
|
||||
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 }
|
||||
|
||||
|
@ -1,6 +1,8 @@
|
||||
local function printUsage()
|
||||
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
|
||||
end
|
||||
|
||||
@ -10,6 +12,23 @@ if #tArgs < 2 then
|
||||
return
|
||||
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]
|
||||
if peripheral.getType(sName) ~= "monitor" then
|
||||
print("No monitor named " .. sName)
|
||||
|
@ -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/mkdir.lua", completion.build({ completion.dir, many = true }))
|
||||
|
||||
local complete_monitor_extra = { "scale" }
|
||||
shell.setCompletionFunction("rom/programs/monitor.lua", completion.build(
|
||||
{ completion.peripheral, true },
|
||||
completion.program
|
||||
function(shell, text, previous)
|
||||
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(
|
||||
{ completion.dirOrFile, true },
|
||||
completion.dirOrFile
|
||||
|
@ -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 );
|
||||
}
|
||||
|
||||
}
|
@ -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 )
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
@ -5,7 +5,7 @@ import dan200.computercraft.ingame.api.TestContext
|
||||
import dan200.computercraft.ingame.api.checkComputerOk
|
||||
|
||||
class TurtleTest {
|
||||
@GameTest(required = false)
|
||||
@GameTest
|
||||
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)
|
||||
|
||||
/**
|
||||
* Checks turtles can place when waterlogged.
|
||||
* Checks turtles can pick up lava
|
||||
*
|
||||
* @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)
|
||||
|
||||
/**
|
||||
* Checks turtles can place when waterlogged.
|
||||
* Checks turtles can hoe dirt.
|
||||
*
|
||||
* @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)
|
||||
|
||||
/**
|
||||
* Checks computers can place into compostors. These are non-typical inventories, so
|
||||
* worth ensuring.
|
||||
* Checks turtles can place into compostors. These are non-typical inventories, so
|
||||
* worth testing.
|
||||
*/
|
||||
@GameTest
|
||||
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)
|
||||
}
|
||||
|
34
src/test/java/dan200/computercraft/utils/CallCounter.java
Normal file
34
src/test/java/dan200/computercraft/utils/CallCounter.java
Normal 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;
|
||||
}
|
||||
}
|
@ -15,3 +15,11 @@ function test.eq(expected, actual, msg)
|
||||
if msg then message = ("%s - %s"):format(msg, message) end
|
||||
test.fail(message)
|
||||
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
|
||||
|
@ -62,6 +62,42 @@ describe("The textutils library", function()
|
||||
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()
|
||||
it("validates arguments", function()
|
||||
textutils.unserialise("")
|
||||
|
@ -3,6 +3,22 @@ local capture = require "test_helpers".capture_program
|
||||
describe("The monitor program", function()
|
||||
it("displays its usage when given no arguments", function()
|
||||
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)
|
||||
|
10
src/test/server-files/computers/computer/12/startup.lua
Normal file
10
src/test/server-files/computers/computer/12/startup.lua
Normal 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()
|
||||
|
@ -1,3 +1,3 @@
|
||||
{
|
||||
"computer": 11
|
||||
"computer": 12
|
||||
}
|
@ -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
|
||||
}
|
@ -10,12 +10,12 @@ table.pretty-table td, table.pretty-table th {
|
||||
}
|
||||
|
||||
table.pretty-table th {
|
||||
background-color: #f0f0f0;
|
||||
background-color: var(--background-2);
|
||||
}
|
||||
|
||||
pre.highlight.highlight-lua {
|
||||
position: relative;
|
||||
background: #eee;
|
||||
background: var(--background-2);
|
||||
padding: 2px;
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user