mirror of
https://github.com/SquidDev-CC/CC-Tweaked
synced 2025-10-23 09:57:39 +00:00
Compare commits
63 Commits
v1.14.3-1.
...
v1.14.4-1.
Author | SHA1 | Date | |
---|---|---|---|
![]() |
3ea2d6a0a8 | ||
![]() |
c802290437 | ||
![]() |
f7781defe5 | ||
![]() |
418420523a | ||
![]() |
d342a1f368 | ||
![]() |
81f85361d5 | ||
![]() |
f1621b30ec | ||
![]() |
d4f6a594b6 | ||
![]() |
ff5ba5c131 | ||
![]() |
4243f30308 | ||
![]() |
813e91073d | ||
![]() |
7250f22ff6 | ||
![]() |
db31a53bba | ||
![]() |
3023f235a4 | ||
![]() |
79cd8b4da5 | ||
![]() |
8e4d311cd9 | ||
![]() |
9bd8c86a94 | ||
![]() |
cbc0c1d0b6 | ||
![]() |
49c37857d4 | ||
![]() |
b1139a4bf6 | ||
![]() |
7e8559278e | ||
![]() |
1e7f1c98fc | ||
![]() |
a802f25dd6 | ||
![]() |
f1d6d21d6d | ||
![]() |
a80302c513 | ||
![]() |
1c46949da7 | ||
![]() |
07a56454a0 | ||
![]() |
a0e72d02c8 | ||
![]() |
455a59ca85 | ||
![]() |
46d78af068 | ||
![]() |
08d22fd3df | ||
![]() |
e6c691a8f8 | ||
![]() |
4b0e5c445c | ||
![]() |
eb5cff1045 | ||
![]() |
35c7792aa2 | ||
![]() |
521688d630 | ||
![]() |
75e2845c01 | ||
![]() |
2f96283286 | ||
![]() |
cbe6e9b5f5 | ||
![]() |
2ab79cf474 | ||
![]() |
6ce34aba79 | ||
![]() |
5eeb320b60 | ||
![]() |
93310850d2 | ||
![]() |
a2880b12ca | ||
![]() |
cef2657048 | ||
![]() |
ccd85eb055 | ||
![]() |
303b57779a | ||
![]() |
6279816ecc | ||
![]() |
4ae77261fa | ||
![]() |
4b7d843b78 | ||
![]() |
1c28df65c3 | ||
![]() |
85b740f484 | ||
![]() |
f9929cb27d | ||
![]() |
bafab1ac07 | ||
![]() |
e05c262468 | ||
![]() |
acfb72246c | ||
![]() |
9d51c4c340 | ||
![]() |
18068effec | ||
![]() |
7a3f7d3bba | ||
![]() |
95aa48c456 | ||
![]() |
904a168d5c | ||
![]() |
724441eddc | ||
![]() |
f68ab3edd1 |
18
.github/workflows/main-ci.yml
vendored
Normal file
18
.github/workflows/main-ci.yml
vendored
Normal file
@@ -0,0 +1,18 @@
|
||||
name: Build
|
||||
|
||||
on: [push, pull_request]
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v1
|
||||
|
||||
- name: Set up JDK 1.8
|
||||
uses: actions/setup-java@v1
|
||||
with:
|
||||
java-version: 1.8
|
||||
|
||||
- name: Build with Gradle
|
||||
run: ./gradlew build --no-daemon
|
@@ -17,7 +17,8 @@ ignore = {
|
||||
-- are largely unsupported.
|
||||
include_files = {
|
||||
'src/main/resources/assets/computercraft/lua/rom',
|
||||
'src/main/resources/assets/computercraft/lua/bios.lua'
|
||||
'src/main/resources/assets/computercraft/lua/bios.lua',
|
||||
'src/test/resources/test-rom',
|
||||
}
|
||||
|
||||
files['src/main/resources/assets/computercraft/lua/bios.lua'] = {
|
||||
|
14
.travis.yml
14
.travis.yml
@@ -1,14 +0,0 @@
|
||||
language: java
|
||||
|
||||
script: ./gradlew build --no-daemon
|
||||
|
||||
before_cache:
|
||||
- rm -f $HOME/.gradle/caches/modules-2/modules-2.lock
|
||||
- rm -fr $HOME/.gradle/caches/*/plugin-resolution/
|
||||
cache:
|
||||
directories:
|
||||
- $HOME/.gradle/caches/
|
||||
- $HOME/.gradle/wrapper/s
|
||||
|
||||
jdk:
|
||||
- oraclejdk8
|
@@ -1,5 +1,5 @@
|
||||
# 
|
||||
[](https://travis-ci.org/SquidDev-CC/CC-Tweaked "Current build status") [](https://minecraft.curseforge.com/projects/cc-tweaked "Download CC: Tweaked on CurseForge")
|
||||
[](https://github.com/SquidDev-CC/CC-Tweaked/actions "Current build status") [](https://minecraft.curseforge.com/projects/cc-tweaked "Download CC: Tweaked on CurseForge")
|
||||
|
||||
CC: Tweaked is a fork of [ComputerCraft](https://github.com/dan200/ComputerCraft), adding programmable computers,
|
||||
turtles and more to Minecraft.
|
||||
|
70
build.gradle
70
build.gradle
@@ -4,12 +4,12 @@ buildscript {
|
||||
mavenCentral()
|
||||
maven {
|
||||
name = "forge"
|
||||
url = "http://files.minecraftforge.net/maven"
|
||||
url = "https://files.minecraftforge.net/maven"
|
||||
}
|
||||
}
|
||||
dependencies {
|
||||
classpath 'com.google.code.gson:gson:2.8.1'
|
||||
classpath 'net.minecraftforge.gradle:ForgeGradle:3.0.128'
|
||||
classpath 'net.minecraftforge.gradle:ForgeGradle:3.0.147'
|
||||
classpath 'net.sf.proguard:proguard-gradle:6.1.0beta2'
|
||||
classpath 'org.ajoberstar.grgit:grgit-gradle:3.0.0'
|
||||
}
|
||||
@@ -67,7 +67,7 @@ minecraft {
|
||||
repositories {
|
||||
maven {
|
||||
name "JEI"
|
||||
url "http://dvs1.progwml6.com/files/maven"
|
||||
url "https://dvs1.progwml6.com/files/maven"
|
||||
}
|
||||
maven {
|
||||
name "SquidDev"
|
||||
@@ -79,7 +79,7 @@ repositories {
|
||||
}
|
||||
maven {
|
||||
name "Amadornes"
|
||||
url "http://maven.amadornes.com/"
|
||||
url "https://maven.amadornes.com/"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -94,16 +94,16 @@ dependencies {
|
||||
|
||||
minecraft "net.minecraftforge:forge:${mc_version}-${forge_version}"
|
||||
|
||||
compileOnly fg.deobf("mezz.jei:jei-1.13.2:5.0.0.20:api")
|
||||
compileOnly fg.deobf("mezz.jei:jei-1.14.4:6.0.0.10:api")
|
||||
// deobfProvided "pl.asie:Charset-Lib:0.5.4.6"
|
||||
// deobfProvided "MCMultiPart2:MCMultiPart:2.5.3"
|
||||
|
||||
// runtimeOnly fg.deobf("mezz.jei:jei-1.13.2:5.0.0.20")
|
||||
runtimeOnly fg.deobf("mezz.jei:jei-1.14.4:6.0.0.10")
|
||||
|
||||
shade 'org.squiddev:Cobalt:0.5.0-SNAPSHOT'
|
||||
|
||||
testImplementation 'org.junit.jupiter:junit-jupiter-api:5.1.0'
|
||||
testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine:5.1.0'
|
||||
testImplementation 'org.junit.jupiter:junit-jupiter-api:5.4.2'
|
||||
testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine:5.4.2'
|
||||
|
||||
deployerJars "org.apache.maven.wagon:wagon-ssh:3.0.0"
|
||||
}
|
||||
@@ -117,6 +117,8 @@ sourceSets {
|
||||
}
|
||||
}
|
||||
|
||||
// Compile tasks
|
||||
|
||||
javadoc {
|
||||
include "dan200/computercraft/api/**/*.java"
|
||||
}
|
||||
@@ -127,7 +129,7 @@ jar {
|
||||
manifest {
|
||||
attributes(["Specification-Title": "computercraft",
|
||||
"Specification-Vendor": "SquidDev",
|
||||
"Specification-Version": "26.0",
|
||||
"Specification-Version": "1",
|
||||
"Implementation-Title": "CC: Tweaked",
|
||||
"Implementation-Version": "${mod_version}",
|
||||
"Implementation-Vendor" :"SquidDev",
|
||||
@@ -141,6 +143,14 @@ jar {
|
||||
from configurations.shade.collect { it.isDirectory() ? it : zipTree(it) }
|
||||
}
|
||||
|
||||
[compileJava, compileTestJava].forEach {
|
||||
it.configure {
|
||||
options.compilerArgs << "-Xlint" << "-Xlint:-processing" << "-Werror"
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
import java.nio.charset.StandardCharsets
|
||||
import java.nio.file.*
|
||||
import java.util.zip.*
|
||||
@@ -276,7 +286,14 @@ task compressJson(dependsOn: jar) {
|
||||
|
||||
assemble.dependsOn compressJson
|
||||
|
||||
/* Check tasks */
|
||||
// Check tasks
|
||||
|
||||
test {
|
||||
useJUnitPlatform()
|
||||
testLogging {
|
||||
events "skipped", "failed"
|
||||
}
|
||||
}
|
||||
|
||||
license {
|
||||
mapping("java", "SLASHSTAR_STYLE")
|
||||
@@ -300,6 +317,13 @@ license {
|
||||
}
|
||||
}
|
||||
|
||||
gradle.projectsEvaluated {
|
||||
tasks.withType(LicenseFormat) {
|
||||
outputs.upToDateWhen { false }
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
task licenseAPI(type: LicenseCheck);
|
||||
task licenseFormatAPI(type: LicenseFormat);
|
||||
[licenseAPI, licenseFormatAPI].forEach {
|
||||
@@ -310,7 +334,7 @@ task licenseFormatAPI(type: LicenseFormat);
|
||||
}
|
||||
}
|
||||
|
||||
/* Upload tasks */
|
||||
// Upload tasks
|
||||
|
||||
task checkRelease {
|
||||
group "upload"
|
||||
@@ -355,7 +379,7 @@ curseforge {
|
||||
apiKey = project.hasProperty('curseForgeApiKey') ? project.curseForgeApiKey : ''
|
||||
project {
|
||||
id = '282001'
|
||||
releaseType = 'beta'
|
||||
releaseType = 'release'
|
||||
changelog = "Release notes can be found on the GitHub repository (https://github.com/SquidDev-CC/CC-Tweaked/releases/tag/v${mc_version}-${mod_version})."
|
||||
|
||||
relations {
|
||||
@@ -431,7 +455,7 @@ githubRelease {
|
||||
.takeWhile { it != 'Type "help changelog" to see the full version history.' }
|
||||
.join("\n").trim()
|
||||
}
|
||||
prerelease true
|
||||
prerelease false
|
||||
}
|
||||
|
||||
def uploadTasks = ["uploadArchives", "curseforge", "githubRelease"]
|
||||
@@ -441,23 +465,3 @@ task uploadAll(dependsOn: uploadTasks) {
|
||||
group "upload"
|
||||
description "Uploads to all repositories (Maven, Curse, GitHub release)"
|
||||
}
|
||||
|
||||
test {
|
||||
useJUnitPlatform()
|
||||
testLogging {
|
||||
events "passed", "skipped", "failed"
|
||||
}
|
||||
}
|
||||
|
||||
gradle.projectsEvaluated {
|
||||
reobfJar.dependsOn proguardMove
|
||||
|
||||
tasks.withType(JavaCompile) {
|
||||
options.compilerArgs << "-Xlint" << "-Xlint:-processing" // Causes Forge build to fail << "-Werror"
|
||||
}
|
||||
|
||||
tasks.withType(LicenseFormat) {
|
||||
outputs.upToDateWhen { false }
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -1,7 +1,7 @@
|
||||
# Mod properties
|
||||
mod_version=1.83.1
|
||||
mod_version=1.85.0
|
||||
|
||||
# Minecraft properties
|
||||
mc_version=1.14.3
|
||||
forge_version=27.0.3
|
||||
mappings_version=20190626-1.14.3
|
||||
mc_version=1.14.4
|
||||
forge_version=28.1.26
|
||||
mappings_version=20190912-1.14.3
|
||||
|
@@ -144,7 +144,9 @@ public interface ITurtleAccess
|
||||
GameProfile getOwningPlayer();
|
||||
|
||||
/**
|
||||
* Get the inventory of this turtle
|
||||
* Get the inventory of this turtle.
|
||||
*
|
||||
* Note: this inventory should only be accessed and modified on the server thread.
|
||||
*
|
||||
* @return This turtle's inventory
|
||||
* @see #getItemHandler()
|
||||
@@ -155,6 +157,8 @@ public interface ITurtleAccess
|
||||
/**
|
||||
* Get the inventory of this turtle as an {@link IItemHandlerModifiable}.
|
||||
*
|
||||
* Note: this inventory should only be accessed and modified on the server thread.
|
||||
*
|
||||
* @return This turtle's inventory
|
||||
* @see #getInventory()
|
||||
* @see IItemHandlerModifiable
|
||||
|
@@ -98,8 +98,8 @@ public interface ITurtleUpgrade
|
||||
* Will only be called for Tool turtle. Called when turtle.dig() or turtle.attack() is called
|
||||
* by the turtle, and the tool is required to do some work.
|
||||
*
|
||||
* Conforming implementations should fire {@link BlockEvent.BreakEvent} and {@link TurtleBlockEvent.Dig}for digging,
|
||||
* {@link AttackEntityEvent} and {@link TurtleAttackEvent} for attacking.
|
||||
* Conforming implementations should fire {@link BlockEvent.BreakEvent} and {@link TurtleBlockEvent.Dig} for
|
||||
* digging, {@link AttackEntityEvent} and {@link TurtleAttackEvent} for attacking.
|
||||
*
|
||||
* @param turtle Access to the turtle that the tool resides on.
|
||||
* @param side Which side of the turtle (left or right) the tool resides on.
|
||||
|
@@ -12,6 +12,7 @@ import dan200.computercraft.shared.common.IColouredItem;
|
||||
import dan200.computercraft.shared.media.items.ItemDisk;
|
||||
import dan200.computercraft.shared.pocket.items.ItemPocketComputer;
|
||||
import dan200.computercraft.shared.util.Colour;
|
||||
import net.minecraft.client.Minecraft;
|
||||
import net.minecraft.client.renderer.model.IBakedModel;
|
||||
import net.minecraft.client.renderer.model.IUnbakedModel;
|
||||
import net.minecraft.client.renderer.model.ModelResourceLocation;
|
||||
@@ -58,8 +59,8 @@ public final class ClientRegistry
|
||||
};
|
||||
|
||||
private static final String[] EXTRA_TEXTURES = new String[] {
|
||||
// TODO: Gather these automatically from the model. I'm unable to get this working with Forge's current
|
||||
// model loading code.
|
||||
// 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",
|
||||
@@ -77,13 +78,12 @@ public final class ClientRegistry
|
||||
@SubscribeEvent
|
||||
public static void onTextureStitchEvent( TextureStitchEvent.Pre event )
|
||||
{
|
||||
/*
|
||||
IResourceManager manager = Minecraft.getInstance().getResourceManager();
|
||||
if( event.getMap() != Minecraft.getInstance().getTextureMap() ) return;
|
||||
|
||||
for( String extra : EXTRA_TEXTURES )
|
||||
{
|
||||
// TODO: event.getMap().registerSprite( manager, new ResourceLocation( ComputerCraft.MOD_ID, extra ) );
|
||||
event.addSprite( new ResourceLocation( ComputerCraft.MOD_ID, extra ) );
|
||||
}
|
||||
*/
|
||||
}
|
||||
|
||||
@SubscribeEvent
|
||||
|
@@ -8,9 +8,9 @@ package dan200.computercraft.client;
|
||||
|
||||
import dan200.computercraft.ComputerCraft;
|
||||
import net.minecraftforge.api.distmarker.Dist;
|
||||
import net.minecraftforge.event.TickEvent;
|
||||
import net.minecraftforge.eventbus.api.SubscribeEvent;
|
||||
import net.minecraftforge.fml.common.Mod;
|
||||
import net.minecraftforge.fml.common.gameevent.TickEvent;
|
||||
|
||||
@Mod.EventBusSubscriber( modid = ComputerCraft.MOD_ID, value = Dist.CLIENT )
|
||||
public final class FrameInfo
|
||||
|
@@ -176,11 +176,4 @@ public final class GuiComputer<T extends ContainerComputerBase> extends Containe
|
||||
return (getFocused() != null && getFocused().mouseDragged( x, y, button, deltaX, deltaY ))
|
||||
|| super.mouseDragged( x, y, button, deltaX, deltaY );
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean mouseReleased( double x, double y, int button )
|
||||
{
|
||||
return (getFocused() != null && getFocused().mouseReleased( x, y, button ))
|
||||
|| super.mouseReleased( x, y, button );
|
||||
}
|
||||
}
|
||||
|
@@ -129,4 +129,11 @@ public class GuiTurtle extends ContainerScreen<ContainerTurtle>
|
||||
super.render( mouseX, mouseY, partialTicks );
|
||||
renderHoveredToolTip( mouseX, mouseY );
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean mouseDragged( double x, double y, int button, double deltaX, double deltaY )
|
||||
{
|
||||
return (getFocused() != null && getFocused().mouseDragged( x, y, button, deltaX, deltaY ))
|
||||
|| super.mouseDragged( x, y, button, deltaX, deltaY );
|
||||
}
|
||||
}
|
||||
|
@@ -241,11 +241,12 @@ public class WidgetTerminal implements IGuiEventListener
|
||||
charX = Math.min( Math.max( charX, 0 ), term.getWidth() - 1 );
|
||||
charY = Math.min( Math.max( charY, 0 ), term.getHeight() - 1 );
|
||||
|
||||
computer.mouseDrag( button + 1, charX + 1, charY + 1 );
|
||||
|
||||
lastMouseX = charX;
|
||||
lastMouseY = charY;
|
||||
lastMouseButton = button;
|
||||
if( button == lastMouseButton && (charX != lastMouseX || charY != lastMouseY) )
|
||||
{
|
||||
computer.mouseDrag( button + 1, charX + 1, charY + 1 );
|
||||
lastMouseX = charX;
|
||||
lastMouseY = charY;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
@@ -427,4 +428,10 @@ public class WidgetTerminal implements IGuiEventListener
|
||||
ClientComputer computer = this.computer.get();
|
||||
if( computer != null ) computer.queueEvent( event, args );
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isMouseOver( double x, double y )
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
@@ -95,4 +95,11 @@ public class WidgetWrapper implements IGuiEventListener
|
||||
{
|
||||
return height;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isMouseOver( double x, double y )
|
||||
{
|
||||
double dx = x - this.x, dy = y - this.y;
|
||||
return dx >= 0 && dx < width && dy >= 0 && dy < height;
|
||||
}
|
||||
}
|
||||
|
@@ -15,7 +15,6 @@ import net.minecraft.block.BlockState;
|
||||
import net.minecraft.client.Minecraft;
|
||||
import net.minecraft.client.renderer.ActiveRenderInfo;
|
||||
import net.minecraft.client.renderer.WorldRenderer;
|
||||
import net.minecraft.entity.player.PlayerEntity;
|
||||
import net.minecraft.util.math.BlockPos;
|
||||
import net.minecraft.util.math.BlockRayTraceResult;
|
||||
import net.minecraft.util.math.RayTraceResult;
|
||||
@@ -39,7 +38,7 @@ public final class CableHighlightRenderer
|
||||
* Draw an outline for a specific part of a cable "Multipart".
|
||||
*
|
||||
* @param event The event to observe
|
||||
* @see WorldRenderer#drawSelectionBox(PlayerEntity, RayTraceResult, int, float)
|
||||
* @see WorldRenderer#drawSelectionBox(ActiveRenderInfo, RayTraceResult, int)
|
||||
*/
|
||||
@SubscribeEvent
|
||||
public static void drawHighlight( DrawBlockHighlightEvent event )
|
||||
@@ -48,7 +47,7 @@ public final class CableHighlightRenderer
|
||||
|
||||
BlockRayTraceResult hit = (BlockRayTraceResult) event.getTarget();
|
||||
BlockPos pos = hit.getPos();
|
||||
World world = event.getInfo().func_216773_g().getEntityWorld();
|
||||
World world = event.getInfo().getRenderViewEntity().getEntityWorld();
|
||||
ActiveRenderInfo info = event.getInfo();
|
||||
|
||||
BlockState state = world.getBlockState( pos );
|
||||
|
@@ -42,12 +42,12 @@ public final class MonitorHighlightRenderer
|
||||
@SubscribeEvent
|
||||
public static void drawHighlight( DrawBlockHighlightEvent event )
|
||||
{
|
||||
if( event.getTarget().getType() != RayTraceResult.Type.BLOCK || event.getInfo().func_216773_g().isSneaking() )
|
||||
if( event.getTarget().getType() != RayTraceResult.Type.BLOCK || event.getInfo().getRenderViewEntity().isSneaking() )
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
World world = event.getInfo().func_216773_g().getEntityWorld();
|
||||
World world = event.getInfo().getRenderViewEntity().getEntityWorld();
|
||||
BlockPos pos = ((BlockRayTraceResult) event.getTarget()).getPos();
|
||||
|
||||
TileEntity tile = world.getTileEntity( pos );
|
||||
|
@@ -119,6 +119,7 @@ public class TurtleMultiModel implements IBakedModel
|
||||
|
||||
@Nonnull
|
||||
@Override
|
||||
@Deprecated
|
||||
public TextureAtlasSprite getParticleTexture()
|
||||
{
|
||||
return m_baseModel.getParticleTexture();
|
||||
|
@@ -202,6 +202,7 @@ public class TurtleSmartItemModel implements IBakedModel
|
||||
|
||||
@Nonnull
|
||||
@Override
|
||||
@Deprecated
|
||||
public TextureAtlasSprite getParticleTexture()
|
||||
{
|
||||
return familyModel.getParticleTexture();
|
||||
|
@@ -34,8 +34,8 @@ 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.ServerWorld;
|
||||
import net.minecraft.world.World;
|
||||
import net.minecraft.world.server.ServerWorld;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
import java.util.*;
|
||||
|
@@ -89,4 +89,10 @@ public abstract class BlockGeneric extends Block
|
||||
{
|
||||
return type.create();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean canBeReplacedByLeaves( BlockState state, IWorldReader world, BlockPos pos )
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
@@ -8,7 +8,6 @@ package dan200.computercraft.shared.common;
|
||||
|
||||
import dan200.computercraft.shared.network.container.ContainerData;
|
||||
import dan200.computercraft.shared.network.container.HeldItemContainerData;
|
||||
import dan200.computercraft.shared.util.InventoryUtil;
|
||||
import net.minecraft.entity.player.PlayerEntity;
|
||||
import net.minecraft.entity.player.PlayerInventory;
|
||||
import net.minecraft.inventory.container.Container;
|
||||
@@ -33,7 +32,7 @@ public class ContainerHeldItem extends Container
|
||||
super( type, id );
|
||||
|
||||
this.hand = hand;
|
||||
stack = InventoryUtil.copyItem( player.getHeldItem( hand ) );
|
||||
stack = player.getHeldItem( hand ).copy();
|
||||
}
|
||||
|
||||
private static ContainerHeldItem createPrintout( int id, PlayerInventory inventory, HeldItemContainerData data )
|
||||
|
@@ -35,7 +35,6 @@ public abstract class TileGeneric extends TileEntity
|
||||
markDirty();
|
||||
BlockPos pos = getPos();
|
||||
BlockState state = getBlockState();
|
||||
getWorld().markForRerender( pos );
|
||||
getWorld().notifyBlockUpdate( pos, state, state, 3 );
|
||||
}
|
||||
|
||||
|
@@ -85,7 +85,7 @@ public class CommandAPI implements ILuaAPI
|
||||
{
|
||||
receiver.clearOutput();
|
||||
int result = commandManager.handleCommand( m_computer.getSource(), command );
|
||||
return new Object[] { result > 0, receiver.copyOutput() };
|
||||
return new Object[] { result > 0, receiver.copyOutput(), result };
|
||||
}
|
||||
catch( Throwable t )
|
||||
{
|
||||
|
@@ -25,8 +25,8 @@ import net.minecraft.util.ResourceLocation;
|
||||
import net.minecraft.util.math.BlockPos;
|
||||
import net.minecraft.util.math.RayTraceResult;
|
||||
import net.minecraft.world.IBlockReader;
|
||||
import net.minecraft.world.ServerWorld;
|
||||
import net.minecraft.world.World;
|
||||
import net.minecraft.world.server.ServerWorld;
|
||||
import net.minecraft.world.storage.loot.LootContext;
|
||||
import net.minecraft.world.storage.loot.LootParameters;
|
||||
|
||||
|
@@ -23,7 +23,7 @@ import net.minecraft.util.text.ITextComponent;
|
||||
import net.minecraft.util.text.StringTextComponent;
|
||||
import net.minecraft.util.text.TranslationTextComponent;
|
||||
import net.minecraft.world.GameRules;
|
||||
import net.minecraft.world.ServerWorld;
|
||||
import net.minecraft.world.server.ServerWorld;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
import java.util.HashMap;
|
||||
|
@@ -311,13 +311,13 @@ public class ServerComputer extends ServerTerminal implements IComputer, IComput
|
||||
@Override
|
||||
public double getTimeOfDay()
|
||||
{
|
||||
return (m_world.getGameTime() + 6000) % 24000 / 1000.0;
|
||||
return (m_world.getDayTime() + 6000) % 24000 / 1000.0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getDay()
|
||||
{
|
||||
return (int) ((m_world.getGameTime() + 6000) / 24000) + 1;
|
||||
return (int) ((m_world.getDayTime() + 6000) / 24000) + 1;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@@ -6,11 +6,16 @@
|
||||
|
||||
package dan200.computercraft.shared.media.items;
|
||||
|
||||
import dan200.computercraft.ComputerCraft;
|
||||
import dan200.computercraft.api.media.IMedia;
|
||||
import dan200.computercraft.shared.util.RecordUtil;
|
||||
import net.minecraft.item.Item;
|
||||
import net.minecraft.item.ItemStack;
|
||||
import net.minecraft.item.MusicDiscItem;
|
||||
import net.minecraft.util.SoundEvent;
|
||||
import net.minecraft.util.text.TranslationTextComponent;
|
||||
import net.minecraftforge.fml.common.ObfuscationReflectionHelper;
|
||||
import net.minecraftforge.fml.common.ObfuscationReflectionHelper.UnableToAccessFieldException;
|
||||
import net.minecraftforge.fml.common.ObfuscationReflectionHelper.UnableToFindFieldException;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
|
||||
@@ -34,12 +39,26 @@ public final class RecordMedia implements IMedia
|
||||
@Override
|
||||
public String getAudioTitle( @Nonnull ItemStack stack )
|
||||
{
|
||||
return RecordUtil.getRecordInfo( stack );
|
||||
Item item = stack.getItem();
|
||||
if( !(item instanceof MusicDiscItem) ) return null;
|
||||
|
||||
return new TranslationTextComponent( item.getTranslationKey() + ".desc" ).getString();
|
||||
}
|
||||
|
||||
@Override
|
||||
public SoundEvent getAudio( @Nonnull ItemStack stack )
|
||||
{
|
||||
return ((MusicDiscItem) stack.getItem()).getSound();
|
||||
Item item = stack.getItem();
|
||||
if( !(item instanceof MusicDiscItem) ) return null;
|
||||
|
||||
try
|
||||
{
|
||||
return ObfuscationReflectionHelper.getPrivateValue( MusicDiscItem.class, (MusicDiscItem) item, "field_185076_b" );
|
||||
}
|
||||
catch( UnableToAccessFieldException | UnableToFindFieldException e )
|
||||
{
|
||||
ComputerCraft.log.error( "Cannot get disk sound", e );
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -11,6 +11,7 @@ import dan200.computercraft.api.lua.LuaException;
|
||||
import dan200.computercraft.api.media.IMedia;
|
||||
import dan200.computercraft.api.peripheral.IComputerAccess;
|
||||
import dan200.computercraft.api.peripheral.IPeripheral;
|
||||
import dan200.computercraft.shared.MediaProviders;
|
||||
import dan200.computercraft.shared.media.items.ItemDisk;
|
||||
import dan200.computercraft.shared.util.StringUtil;
|
||||
import net.minecraft.item.ItemStack;
|
||||
@@ -19,11 +20,11 @@ import javax.annotation.Nonnull;
|
||||
|
||||
import static dan200.computercraft.core.apis.ArgumentHelper.optString;
|
||||
|
||||
public class DiskDrivePeripheral implements IPeripheral
|
||||
class DiskDrivePeripheral implements IPeripheral
|
||||
{
|
||||
private final TileDiskDrive m_diskDrive;
|
||||
|
||||
public DiskDrivePeripheral( TileDiskDrive diskDrive )
|
||||
DiskDrivePeripheral( TileDiskDrive diskDrive )
|
||||
{
|
||||
m_diskDrive = diskDrive;
|
||||
}
|
||||
@@ -55,7 +56,7 @@ public class DiskDrivePeripheral implements IPeripheral
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object[] callMethod( @Nonnull IComputerAccess computer, @Nonnull ILuaContext context, int method, @Nonnull Object[] arguments ) throws LuaException
|
||||
public Object[] callMethod( @Nonnull IComputerAccess computer, @Nonnull ILuaContext context, int method, @Nonnull Object[] arguments ) throws LuaException, InterruptedException
|
||||
{
|
||||
switch( method )
|
||||
{
|
||||
@@ -63,21 +64,26 @@ public class DiskDrivePeripheral implements IPeripheral
|
||||
return new Object[] { !m_diskDrive.getDiskStack().isEmpty() };
|
||||
case 1: // getDiskLabel
|
||||
{
|
||||
IMedia media = m_diskDrive.getDiskMedia();
|
||||
return media == null ? null : new Object[] { media.getLabel( m_diskDrive.getDiskStack() ) };
|
||||
ItemStack stack = m_diskDrive.getDiskStack();
|
||||
IMedia media = MediaProviders.get( stack );
|
||||
return media == null ? null : new Object[] { media.getLabel( stack ) };
|
||||
}
|
||||
case 2: // setDiskLabel
|
||||
{
|
||||
String label = optString( arguments, 0, null );
|
||||
|
||||
IMedia media = m_diskDrive.getDiskMedia();
|
||||
if( media == null ) return null;
|
||||
return context.executeMainThreadTask( () -> {
|
||||
ItemStack stack = m_diskDrive.getDiskStack();
|
||||
IMedia media = MediaProviders.get( stack );
|
||||
if( media == null ) return null;
|
||||
|
||||
ItemStack disk = m_diskDrive.getDiskStack();
|
||||
label = StringUtil.normaliseLabel( label );
|
||||
if( !media.setLabel( disk, label ) ) throw new LuaException( "Disk label cannot be changed" );
|
||||
m_diskDrive.setDiskStack( disk );
|
||||
return null;
|
||||
if( !media.setLabel( stack, StringUtil.normaliseLabel( label ) ) )
|
||||
{
|
||||
throw new LuaException( "Disk label cannot be changed" );
|
||||
}
|
||||
m_diskDrive.setDiskStack( stack );
|
||||
return null;
|
||||
} );
|
||||
}
|
||||
case 3: // hasData
|
||||
return new Object[] { m_diskDrive.getDiskMountPath( computer ) != null };
|
||||
@@ -86,14 +92,16 @@ public class DiskDrivePeripheral implements IPeripheral
|
||||
case 5:
|
||||
{
|
||||
// hasAudio
|
||||
IMedia media = m_diskDrive.getDiskMedia();
|
||||
return new Object[] { media != null && media.getAudio( m_diskDrive.getDiskStack() ) != null };
|
||||
ItemStack stack = m_diskDrive.getDiskStack();
|
||||
IMedia media = MediaProviders.get( stack );
|
||||
return new Object[] { media != null && media.getAudio( stack ) != null };
|
||||
}
|
||||
case 6:
|
||||
{
|
||||
// getAudioTitle
|
||||
IMedia media = m_diskDrive.getDiskMedia();
|
||||
return new Object[] { media != null ? media.getAudioTitle( m_diskDrive.getDiskStack() ) : false };
|
||||
ItemStack stack = m_diskDrive.getDiskStack();
|
||||
IMedia media = MediaProviders.get( stack );
|
||||
return new Object[] { media != null ? media.getAudioTitle( stack ) : false };
|
||||
}
|
||||
case 7: // playAudio
|
||||
m_diskDrive.playDiskAudio();
|
||||
@@ -129,8 +137,7 @@ public class DiskDrivePeripheral implements IPeripheral
|
||||
@Override
|
||||
public boolean equals( IPeripheral other )
|
||||
{
|
||||
if( this == other ) return true;
|
||||
return other instanceof DiskDrivePeripheral && ((DiskDrivePeripheral) other).m_diskDrive == m_diskDrive;
|
||||
return this == other || other instanceof DiskDrivePeripheral && ((DiskDrivePeripheral) other).m_diskDrive == m_diskDrive;
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
|
@@ -320,35 +320,31 @@ public final class TileDiskDrive extends TileGeneric implements DefaultInventory
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
public ItemStack getDiskStack()
|
||||
ItemStack getDiskStack()
|
||||
{
|
||||
return getStackInSlot( 0 );
|
||||
}
|
||||
|
||||
public void setDiskStack( @Nonnull ItemStack stack )
|
||||
void setDiskStack( @Nonnull ItemStack stack )
|
||||
{
|
||||
setInventorySlotContents( 0, stack );
|
||||
}
|
||||
|
||||
public IMedia getDiskMedia()
|
||||
private IMedia getDiskMedia()
|
||||
{
|
||||
return MediaProviders.get( getDiskStack() );
|
||||
}
|
||||
|
||||
public String getDiskMountPath( IComputerAccess computer )
|
||||
String getDiskMountPath( IComputerAccess computer )
|
||||
{
|
||||
synchronized( this )
|
||||
{
|
||||
if( m_computers.containsKey( computer ) )
|
||||
{
|
||||
MountInfo info = m_computers.get( computer );
|
||||
return info.mountPath;
|
||||
}
|
||||
MountInfo info = m_computers.get( computer );
|
||||
return info != null ? info.mountPath : null;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public void mount( IComputerAccess computer )
|
||||
void mount( IComputerAccess computer )
|
||||
{
|
||||
synchronized( this )
|
||||
{
|
||||
@@ -357,7 +353,7 @@ public final class TileDiskDrive extends TileGeneric implements DefaultInventory
|
||||
}
|
||||
}
|
||||
|
||||
public void unmount( IComputerAccess computer )
|
||||
void unmount( IComputerAccess computer )
|
||||
{
|
||||
synchronized( this )
|
||||
{
|
||||
@@ -366,7 +362,7 @@ public final class TileDiskDrive extends TileGeneric implements DefaultInventory
|
||||
}
|
||||
}
|
||||
|
||||
public void playDiskAudio()
|
||||
void playDiskAudio()
|
||||
{
|
||||
synchronized( this )
|
||||
{
|
||||
@@ -379,7 +375,7 @@ public final class TileDiskDrive extends TileGeneric implements DefaultInventory
|
||||
}
|
||||
}
|
||||
|
||||
public void stopDiskAudio()
|
||||
void stopDiskAudio()
|
||||
{
|
||||
synchronized( this )
|
||||
{
|
||||
@@ -388,7 +384,7 @@ public final class TileDiskDrive extends TileGeneric implements DefaultInventory
|
||||
}
|
||||
}
|
||||
|
||||
public void ejectDisk()
|
||||
void ejectDisk()
|
||||
{
|
||||
synchronized( this )
|
||||
{
|
||||
@@ -509,28 +505,6 @@ public final class TileDiskDrive extends TileGeneric implements DefaultInventory
|
||||
if( !destroyed ) getWorld().playBroadcastSound( 1000, getPos(), 0 );
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void readDescription( @Nonnull CompoundNBT nbt )
|
||||
{
|
||||
super.readDescription( nbt );
|
||||
customName = nbt.contains( NBT_NAME ) ? ITextComponent.Serializer.fromJson( nbt.getString( NBT_NAME ) ) : null;
|
||||
m_diskStack = nbt.contains( NBT_ITEM ) ? ItemStack.read( nbt.getCompound( NBT_ITEM ) ) : ItemStack.EMPTY;
|
||||
updateBlock();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void writeDescription( @Nonnull CompoundNBT nbt )
|
||||
{
|
||||
super.writeDescription( nbt );
|
||||
if( customName != null ) nbt.putString( NBT_NAME, ITextComponent.Serializer.toJson( customName ) );
|
||||
if( !m_diskStack.isEmpty() )
|
||||
{
|
||||
CompoundNBT item = new CompoundNBT();
|
||||
m_diskStack.write( item );
|
||||
nbt.put( NBT_ITEM, item );
|
||||
}
|
||||
}
|
||||
|
||||
// Private methods
|
||||
|
||||
private void playRecord()
|
||||
|
@@ -210,7 +210,7 @@ public class BlockCable extends BlockGeneric implements IWaterLoggable
|
||||
|
||||
BlockPos offsetPos = pos.offset( facing );
|
||||
BlockState offsetState = world.getBlockState( offsetPos );
|
||||
return Block.hasSolidSide( offsetState, world, offsetPos, facing.getOpposite() );
|
||||
return hasSolidSide( offsetState, world, offsetPos, facing.getOpposite() );
|
||||
}
|
||||
|
||||
@Nullable
|
||||
|
@@ -201,8 +201,7 @@ public class TileCable extends TileGeneric implements IPeripheralTile
|
||||
public void onNeighbourChange( @Nonnull BlockPos neighbour )
|
||||
{
|
||||
Direction dir = getDirection();
|
||||
if( neighbour.equals( getPos().offset( dir ) ) && hasModem()
|
||||
&& getBlockState().isValidPosition( world, pos ) )
|
||||
if( neighbour.equals( getPos().offset( dir ) ) && hasModem() && !getBlockState().isValidPosition( getWorld(), getPos() ) )
|
||||
{
|
||||
if( hasCable() )
|
||||
{
|
||||
|
@@ -34,7 +34,7 @@ public class ContainerPrinter extends Container
|
||||
this.properties = properties;
|
||||
this.inventory = inventory;
|
||||
|
||||
func_216961_a( properties );
|
||||
trackIntArray( properties );
|
||||
|
||||
// Ink slot
|
||||
addSlot( new Slot( inventory, 0, 13, 35 ) );
|
||||
|
@@ -11,6 +11,7 @@ import dan200.computercraft.api.lua.LuaException;
|
||||
import dan200.computercraft.api.peripheral.IComputerAccess;
|
||||
import dan200.computercraft.api.peripheral.IPeripheral;
|
||||
import dan200.computercraft.core.terminal.Terminal;
|
||||
import dan200.computercraft.shared.util.StringUtil;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
|
||||
@@ -51,8 +52,13 @@ public class PrinterPeripheral implements IPeripheral
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object[] callMethod( @Nonnull IComputerAccess computer, @Nonnull ILuaContext context, int method, @Nonnull Object[] args ) throws LuaException
|
||||
public Object[] callMethod( @Nonnull IComputerAccess computer, @Nonnull ILuaContext context, int method, @Nonnull Object[] args ) throws LuaException, InterruptedException
|
||||
{
|
||||
// FIXME: There's a theoretical race condition here between getCurrentPage and then using the page. Ideally
|
||||
// we'd lock on the page, consume it, and unlock.
|
||||
|
||||
// FIXME: None of our page modification functions actually mark the tile as dirty, so the page may not be
|
||||
// persisted correctly.
|
||||
switch( method )
|
||||
{
|
||||
case 0: // write
|
||||
@@ -89,10 +95,13 @@ public class PrinterPeripheral implements IPeripheral
|
||||
return new Object[] { width, height };
|
||||
}
|
||||
case 4: // newPage
|
||||
return new Object[] { m_printer.startNewPage() };
|
||||
return context.executeMainThreadTask( () -> new Object[] { m_printer.startNewPage() } );
|
||||
case 5: // endPage
|
||||
getCurrentPage();
|
||||
return new Object[] { m_printer.endCurrentPage() };
|
||||
return context.executeMainThreadTask( () -> {
|
||||
getCurrentPage();
|
||||
return new Object[] { m_printer.endCurrentPage() };
|
||||
} );
|
||||
case 6: // getInkLevel
|
||||
return new Object[] { m_printer.getInkLevel() };
|
||||
case 7:
|
||||
@@ -100,7 +109,7 @@ public class PrinterPeripheral implements IPeripheral
|
||||
// setPageTitle
|
||||
String title = optString( args, 0, "" );
|
||||
getCurrentPage();
|
||||
m_printer.setPageTitle( title );
|
||||
m_printer.setPageTitle( StringUtil.normaliseLabel( title ) );
|
||||
return null;
|
||||
}
|
||||
case 8: // getPaperLevel
|
||||
@@ -123,13 +132,11 @@ public class PrinterPeripheral implements IPeripheral
|
||||
return m_printer;
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
private Terminal getCurrentPage() throws LuaException
|
||||
{
|
||||
Terminal currentPage = m_printer.getCurrentPage();
|
||||
if( currentPage == null )
|
||||
{
|
||||
throw new LuaException( "Page not started" );
|
||||
}
|
||||
if( currentPage == null ) throw new LuaException( "Page not started" );
|
||||
return currentPage;
|
||||
}
|
||||
}
|
||||
|
@@ -120,10 +120,7 @@ public final class TilePrinter extends TileGeneric implements DefaultSidedInvent
|
||||
}
|
||||
|
||||
// Read inventory
|
||||
synchronized( m_inventory )
|
||||
{
|
||||
ItemStackHelper.loadAllItems( nbt, m_inventory );
|
||||
}
|
||||
ItemStackHelper.loadAllItems( nbt, m_inventory );
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
@@ -141,30 +138,12 @@ public final class TilePrinter extends TileGeneric implements DefaultSidedInvent
|
||||
}
|
||||
|
||||
// Write inventory
|
||||
synchronized( m_inventory )
|
||||
{
|
||||
ItemStackHelper.saveAllItems( nbt, m_inventory );
|
||||
}
|
||||
ItemStackHelper.saveAllItems( nbt, m_inventory );
|
||||
|
||||
return super.write( nbt );
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void writeDescription( @Nonnull CompoundNBT nbt )
|
||||
{
|
||||
super.writeDescription( nbt );
|
||||
if( customName != null ) nbt.putString( NBT_NAME, ITextComponent.Serializer.toJson( customName ) );
|
||||
}
|
||||
|
||||
@Override
|
||||
public void readDescription( @Nonnull CompoundNBT nbt )
|
||||
{
|
||||
super.readDescription( nbt );
|
||||
customName = nbt.contains( NBT_NAME ) ? ITextComponent.Serializer.fromJson( nbt.getString( NBT_NAME ) ) : null;
|
||||
updateBlock();
|
||||
}
|
||||
|
||||
public boolean isPrinting()
|
||||
boolean isPrinting()
|
||||
{
|
||||
return m_printing;
|
||||
}
|
||||
@@ -188,73 +167,59 @@ public final class TilePrinter extends TileGeneric implements DefaultSidedInvent
|
||||
|
||||
@Nonnull
|
||||
@Override
|
||||
public ItemStack getStackInSlot( int i )
|
||||
public ItemStack getStackInSlot( int slot )
|
||||
{
|
||||
return m_inventory.get( i );
|
||||
return m_inventory.get( slot );
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
@Override
|
||||
public ItemStack removeStackFromSlot( int i )
|
||||
public ItemStack removeStackFromSlot( int slot )
|
||||
{
|
||||
synchronized( m_inventory )
|
||||
{
|
||||
ItemStack result = m_inventory.get( i );
|
||||
m_inventory.set( i, ItemStack.EMPTY );
|
||||
markDirty();
|
||||
updateBlockState();
|
||||
return result;
|
||||
}
|
||||
ItemStack result = m_inventory.get( slot );
|
||||
m_inventory.set( slot, ItemStack.EMPTY );
|
||||
markDirty();
|
||||
updateBlockState();
|
||||
return result;
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
@Override
|
||||
public ItemStack decrStackSize( int i, int j )
|
||||
public ItemStack decrStackSize( int slot, int count )
|
||||
{
|
||||
synchronized( m_inventory )
|
||||
ItemStack stack = m_inventory.get( slot );
|
||||
if( stack.isEmpty() ) return ItemStack.EMPTY;
|
||||
|
||||
if( stack.getCount() <= count )
|
||||
{
|
||||
if( m_inventory.get( i ).isEmpty() ) return ItemStack.EMPTY;
|
||||
|
||||
if( m_inventory.get( i ).getCount() <= j )
|
||||
{
|
||||
ItemStack itemstack = m_inventory.get( i );
|
||||
m_inventory.set( i, ItemStack.EMPTY );
|
||||
markDirty();
|
||||
updateBlockState();
|
||||
return itemstack;
|
||||
}
|
||||
|
||||
ItemStack part = m_inventory.get( i ).split( j );
|
||||
if( m_inventory.get( i ).isEmpty() )
|
||||
{
|
||||
m_inventory.set( i, ItemStack.EMPTY );
|
||||
updateBlockState();
|
||||
}
|
||||
markDirty();
|
||||
return part;
|
||||
setInventorySlotContents( slot, ItemStack.EMPTY );
|
||||
return stack;
|
||||
}
|
||||
|
||||
ItemStack part = stack.split( count );
|
||||
if( m_inventory.get( slot ).isEmpty() )
|
||||
{
|
||||
m_inventory.set( slot, ItemStack.EMPTY );
|
||||
updateBlockState();
|
||||
}
|
||||
markDirty();
|
||||
return part;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setInventorySlotContents( int i, @Nonnull ItemStack stack )
|
||||
public void setInventorySlotContents( int slot, @Nonnull ItemStack stack )
|
||||
{
|
||||
synchronized( m_inventory )
|
||||
{
|
||||
m_inventory.set( i, stack );
|
||||
markDirty();
|
||||
updateBlockState();
|
||||
}
|
||||
m_inventory.set( slot, stack );
|
||||
markDirty();
|
||||
updateBlockState();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void clear()
|
||||
{
|
||||
synchronized( m_inventory )
|
||||
{
|
||||
for( int i = 0; i < m_inventory.size(); i++ ) m_inventory.set( i, ItemStack.EMPTY );
|
||||
markDirty();
|
||||
updateBlockState();
|
||||
}
|
||||
for( int i = 0; i < m_inventory.size(); i++ ) m_inventory.set( i, ItemStack.EMPTY );
|
||||
markDirty();
|
||||
updateBlockState();
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -305,14 +270,18 @@ public final class TilePrinter extends TileGeneric implements DefaultSidedInvent
|
||||
return new PrinterPeripheral( this );
|
||||
}
|
||||
|
||||
public Terminal getCurrentPage()
|
||||
@Nullable
|
||||
Terminal getCurrentPage()
|
||||
{
|
||||
return m_printing ? m_page : null;
|
||||
synchronized( m_page )
|
||||
{
|
||||
return m_printing ? m_page : null;
|
||||
}
|
||||
}
|
||||
|
||||
public boolean startNewPage()
|
||||
boolean startNewPage()
|
||||
{
|
||||
synchronized( m_inventory )
|
||||
synchronized( m_page )
|
||||
{
|
||||
if( !canInputPage() ) return false;
|
||||
if( m_printing && !outputPage() ) return false;
|
||||
@@ -320,49 +289,36 @@ public final class TilePrinter extends TileGeneric implements DefaultSidedInvent
|
||||
}
|
||||
}
|
||||
|
||||
public boolean endCurrentPage()
|
||||
boolean endCurrentPage()
|
||||
{
|
||||
synchronized( m_inventory )
|
||||
synchronized( m_page )
|
||||
{
|
||||
if( m_printing && outputPage() )
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public int getInkLevel()
|
||||
{
|
||||
synchronized( m_inventory )
|
||||
{
|
||||
ItemStack inkStack = m_inventory.get( 0 );
|
||||
return isInk( inkStack ) ? inkStack.getCount() : 0;
|
||||
return m_printing && outputPage();
|
||||
}
|
||||
}
|
||||
|
||||
public int getPaperLevel()
|
||||
int getInkLevel()
|
||||
{
|
||||
ItemStack inkStack = m_inventory.get( 0 );
|
||||
return isInk( inkStack ) ? inkStack.getCount() : 0;
|
||||
}
|
||||
|
||||
int getPaperLevel()
|
||||
{
|
||||
int count = 0;
|
||||
synchronized( m_inventory )
|
||||
for( int i = 1; i < 7; i++ )
|
||||
{
|
||||
for( int i = 1; i < 7; i++ )
|
||||
{
|
||||
ItemStack paperStack = m_inventory.get( i );
|
||||
if( !paperStack.isEmpty() && isPaper( paperStack ) )
|
||||
{
|
||||
count += paperStack.getCount();
|
||||
}
|
||||
}
|
||||
ItemStack paperStack = m_inventory.get( i );
|
||||
if( isPaper( paperStack ) ) count += paperStack.getCount();
|
||||
}
|
||||
return count;
|
||||
}
|
||||
|
||||
public void setPageTitle( String title )
|
||||
void setPageTitle( String title )
|
||||
{
|
||||
if( m_printing )
|
||||
synchronized( m_page )
|
||||
{
|
||||
m_pageTitle = title;
|
||||
if( m_printing ) m_pageTitle = title;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -380,116 +336,100 @@ public final class TilePrinter extends TileGeneric implements DefaultSidedInvent
|
||||
|
||||
private boolean canInputPage()
|
||||
{
|
||||
synchronized( m_inventory )
|
||||
{
|
||||
ItemStack inkStack = m_inventory.get( 0 );
|
||||
return !inkStack.isEmpty() && isInk( inkStack ) && getPaperLevel() > 0;
|
||||
}
|
||||
ItemStack inkStack = m_inventory.get( 0 );
|
||||
return !inkStack.isEmpty() && isInk( inkStack ) && getPaperLevel() > 0;
|
||||
}
|
||||
|
||||
private boolean inputPage()
|
||||
{
|
||||
synchronized( m_inventory )
|
||||
ItemStack inkStack = m_inventory.get( 0 );
|
||||
if( !isInk( inkStack ) ) return false;
|
||||
|
||||
for( int i = 1; i < 7; i++ )
|
||||
{
|
||||
ItemStack inkStack = m_inventory.get( 0 );
|
||||
if( !isInk( inkStack ) ) return false;
|
||||
ItemStack paperStack = m_inventory.get( i );
|
||||
if( paperStack.isEmpty() || !isPaper( paperStack ) ) continue;
|
||||
|
||||
for( int i = 1; i < 7; i++ )
|
||||
// Setup the new page
|
||||
DyeColor dye = ColourUtils.getStackColour( inkStack );
|
||||
m_page.setTextColour( dye != null ? dye.getId() : 15 );
|
||||
|
||||
m_page.clear();
|
||||
if( paperStack.getItem() instanceof ItemPrintout )
|
||||
{
|
||||
ItemStack paperStack = m_inventory.get( i );
|
||||
if( !paperStack.isEmpty() && isPaper( paperStack ) )
|
||||
m_pageTitle = ItemPrintout.getTitle( paperStack );
|
||||
String[] text = ItemPrintout.getText( paperStack );
|
||||
String[] textColour = ItemPrintout.getColours( paperStack );
|
||||
for( int y = 0; y < m_page.getHeight(); y++ )
|
||||
{
|
||||
// Setup the new page
|
||||
DyeColor dye = ColourUtils.getStackColour( inkStack );
|
||||
m_page.setTextColour( dye != null ? dye.getId() : 15 );
|
||||
|
||||
m_page.clear();
|
||||
if( paperStack.getItem() instanceof ItemPrintout )
|
||||
{
|
||||
m_pageTitle = ItemPrintout.getTitle( paperStack );
|
||||
String[] text = ItemPrintout.getText( paperStack );
|
||||
String[] textColour = ItemPrintout.getColours( paperStack );
|
||||
for( int y = 0; y < m_page.getHeight(); y++ )
|
||||
{
|
||||
m_page.setLine( y, text[y], textColour[y], "" );
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
m_pageTitle = "";
|
||||
}
|
||||
m_page.setCursorPos( 0, 0 );
|
||||
|
||||
// Decrement ink
|
||||
inkStack.shrink( 1 );
|
||||
if( inkStack.isEmpty() ) m_inventory.set( 0, ItemStack.EMPTY );
|
||||
|
||||
// Decrement paper
|
||||
paperStack.shrink( 1 );
|
||||
if( paperStack.isEmpty() )
|
||||
{
|
||||
m_inventory.set( i, ItemStack.EMPTY );
|
||||
updateBlockState();
|
||||
}
|
||||
|
||||
markDirty();
|
||||
m_printing = true;
|
||||
return true;
|
||||
m_page.setLine( y, text[y], textColour[y], "" );
|
||||
}
|
||||
}
|
||||
return false;
|
||||
else
|
||||
{
|
||||
m_pageTitle = "";
|
||||
}
|
||||
m_page.setCursorPos( 0, 0 );
|
||||
|
||||
// Decrement ink
|
||||
inkStack.shrink( 1 );
|
||||
if( inkStack.isEmpty() ) m_inventory.set( 0, ItemStack.EMPTY );
|
||||
|
||||
// Decrement paper
|
||||
paperStack.shrink( 1 );
|
||||
if( paperStack.isEmpty() )
|
||||
{
|
||||
m_inventory.set( i, ItemStack.EMPTY );
|
||||
updateBlockState();
|
||||
}
|
||||
|
||||
markDirty();
|
||||
m_printing = true;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private boolean outputPage()
|
||||
{
|
||||
synchronized( m_page )
|
||||
int height = m_page.getHeight();
|
||||
String[] lines = new String[height];
|
||||
String[] colours = new String[height];
|
||||
for( int i = 0; i < height; i++ )
|
||||
{
|
||||
int height = m_page.getHeight();
|
||||
String[] lines = new String[height];
|
||||
String[] colours = new String[height];
|
||||
for( int i = 0; i < height; i++ )
|
||||
{
|
||||
lines[i] = m_page.getLine( i ).toString();
|
||||
colours[i] = m_page.getTextColourLine( i ).toString();
|
||||
}
|
||||
|
||||
ItemStack stack = ItemPrintout.createSingleFromTitleAndText( m_pageTitle, lines, colours );
|
||||
synchronized( m_inventory )
|
||||
{
|
||||
for( int slot : BOTTOM_SLOTS )
|
||||
{
|
||||
if( m_inventory.get( slot ).isEmpty() )
|
||||
{
|
||||
setInventorySlotContents( slot, stack );
|
||||
m_printing = false;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
lines[i] = m_page.getLine( i ).toString();
|
||||
colours[i] = m_page.getTextColourLine( i ).toString();
|
||||
}
|
||||
|
||||
ItemStack stack = ItemPrintout.createSingleFromTitleAndText( m_pageTitle, lines, colours );
|
||||
for( int slot : BOTTOM_SLOTS )
|
||||
{
|
||||
if( m_inventory.get( slot ).isEmpty() )
|
||||
{
|
||||
setInventorySlotContents( slot, stack );
|
||||
m_printing = false;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private void ejectContents()
|
||||
{
|
||||
synchronized( m_inventory )
|
||||
for( int i = 0; i < 13; i++ )
|
||||
{
|
||||
for( int i = 0; i < 13; i++ )
|
||||
ItemStack stack = m_inventory.get( i );
|
||||
if( !stack.isEmpty() )
|
||||
{
|
||||
ItemStack stack = m_inventory.get( i );
|
||||
if( !stack.isEmpty() )
|
||||
{
|
||||
// Remove the stack from the inventory
|
||||
setInventorySlotContents( i, ItemStack.EMPTY );
|
||||
// Remove the stack from the inventory
|
||||
setInventorySlotContents( i, ItemStack.EMPTY );
|
||||
|
||||
// Spawn the item in the world
|
||||
BlockPos pos = getPos();
|
||||
double x = pos.getX() + 0.5;
|
||||
double y = pos.getY() + 0.75;
|
||||
double z = pos.getZ() + 0.5;
|
||||
WorldUtil.dropItemStack( stack, getWorld(), x, y, z );
|
||||
}
|
||||
// Spawn the item in the world
|
||||
BlockPos pos = getPos();
|
||||
double x = pos.getX() + 0.5;
|
||||
double y = pos.getY() + 0.75;
|
||||
double z = pos.getZ() + 0.5;
|
||||
WorldUtil.dropItemStack( stack, getWorld(), x, y, z );
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -497,25 +437,22 @@ public final class TilePrinter extends TileGeneric implements DefaultSidedInvent
|
||||
private void updateBlockState()
|
||||
{
|
||||
boolean top = false, bottom = false;
|
||||
synchronized( m_inventory )
|
||||
for( int i = 1; i < 7; i++ )
|
||||
{
|
||||
for( int i = 1; i < 7; i++ )
|
||||
ItemStack stack = m_inventory.get( i );
|
||||
if( !stack.isEmpty() && isPaper( stack ) )
|
||||
{
|
||||
ItemStack stack = m_inventory.get( i );
|
||||
if( !stack.isEmpty() && isPaper( stack ) )
|
||||
{
|
||||
top = true;
|
||||
break;
|
||||
}
|
||||
top = true;
|
||||
break;
|
||||
}
|
||||
for( int i = 7; i < 13; i++ )
|
||||
}
|
||||
for( int i = 7; i < 13; i++ )
|
||||
{
|
||||
ItemStack stack = m_inventory.get( i );
|
||||
if( !stack.isEmpty() && isPaper( stack ) )
|
||||
{
|
||||
ItemStack stack = m_inventory.get( i );
|
||||
if( !stack.isEmpty() && isPaper( stack ) )
|
||||
{
|
||||
bottom = true;
|
||||
break;
|
||||
}
|
||||
bottom = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -34,11 +34,11 @@ import net.minecraft.tileentity.CommandBlockTileEntity;
|
||||
import net.minecraft.tileentity.TileEntity;
|
||||
import net.minecraft.util.ResourceLocation;
|
||||
import net.minecraft.world.storage.loot.conditions.LootConditionManager;
|
||||
import net.minecraftforge.event.TickEvent;
|
||||
import net.minecraftforge.event.entity.player.PlayerContainerEvent;
|
||||
import net.minecraftforge.eventbus.api.SubscribeEvent;
|
||||
import net.minecraftforge.fml.client.event.ConfigChangedEvent;
|
||||
import net.minecraftforge.fml.common.Mod;
|
||||
import net.minecraftforge.fml.common.gameevent.TickEvent;
|
||||
import net.minecraftforge.fml.event.lifecycle.FMLCommonSetupEvent;
|
||||
import net.minecraftforge.fml.event.server.FMLServerStartedEvent;
|
||||
import net.minecraftforge.fml.event.server.FMLServerStartingEvent;
|
||||
@@ -146,7 +146,7 @@ public final class ComputerCraftProxyCommon
|
||||
IComputer computer = ((IContainerComputer) container).getComputer();
|
||||
if( computer instanceof ServerComputer )
|
||||
{
|
||||
((ServerComputer) computer).sendTerminalState( event.getEntityPlayer() );
|
||||
((ServerComputer) computer).sendTerminalState( event.getPlayer() );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -12,8 +12,7 @@ import dan200.computercraft.api.turtle.event.TurtleRefuelEvent;
|
||||
import dan200.computercraft.shared.util.InventoryUtil;
|
||||
import dan200.computercraft.shared.util.WorldUtil;
|
||||
import net.minecraft.item.ItemStack;
|
||||
import net.minecraft.tileentity.FurnaceTileEntity;
|
||||
import net.minecraftforge.event.ForgeEventFactory;
|
||||
import net.minecraftforge.common.ForgeHooks;
|
||||
import net.minecraftforge.eventbus.api.SubscribeEvent;
|
||||
import net.minecraftforge.fml.common.Mod;
|
||||
|
||||
@@ -31,9 +30,13 @@ public final class FurnaceRefuelHandler implements TurtleRefuelEvent.Handler
|
||||
@Override
|
||||
public int refuel( @Nonnull ITurtleAccess turtle, @Nonnull ItemStack currentStack, int slot, int limit )
|
||||
{
|
||||
ItemStack stack = turtle.getItemHandler().extractItem( slot, limit, false );
|
||||
int fuelToGive = getFuelPerItem( stack ) * stack.getCount();
|
||||
int fuelSpaceLeft = turtle.getFuelLimit() - turtle.getFuelLevel();
|
||||
int fuelPerItem = getFuelPerItem( turtle.getItemHandler().getStackInSlot( slot ) );
|
||||
int fuelItemLimit = (int) Math.ceil( fuelSpaceLeft / (double) fuelPerItem );
|
||||
if( limit > fuelItemLimit ) limit = fuelItemLimit;
|
||||
|
||||
ItemStack stack = turtle.getItemHandler().extractItem( slot, limit, false );
|
||||
int fuelToGive = fuelPerItem * stack.getCount();
|
||||
// Store the replacement item in the inventory
|
||||
ItemStack replacementStack = stack.getItem().getContainerItem( stack );
|
||||
if( !replacementStack.isEmpty() )
|
||||
@@ -48,15 +51,9 @@ public final class FurnaceRefuelHandler implements TurtleRefuelEvent.Handler
|
||||
return fuelToGive;
|
||||
}
|
||||
|
||||
|
||||
private static int getFuelPerItem( @Nonnull ItemStack stack )
|
||||
{
|
||||
int basicBurnTime = stack.getBurnTime();
|
||||
int burnTime = ForgeEventFactory.getItemBurnTime(
|
||||
stack,
|
||||
basicBurnTime == -1 ? FurnaceTileEntity.getBurnTimes().getOrDefault( stack.getItem(), 0 ) : basicBurnTime
|
||||
);
|
||||
return (burnTime * 5) / 100;
|
||||
return (ForgeHooks.getBurnTime( stack ) * 5) / 100;
|
||||
}
|
||||
|
||||
@SubscribeEvent
|
||||
|
@@ -335,9 +335,11 @@ public class TurtleAPI implements ILuaAPI
|
||||
return tryCommand( context, new TurtleInspectCommand( InteractDirection.Up ) );
|
||||
case 40: // inspectDown
|
||||
return tryCommand( context, new TurtleInspectCommand( InteractDirection.Down ) );
|
||||
case 41:
|
||||
case 41: // getItemDetail
|
||||
{
|
||||
// getItemDetail
|
||||
// FIXME: There's a race condition here if the stack is being modified (mutating NBT, etc...)
|
||||
// on another thread. The obvious solution is to move this into a command, but some programs rely
|
||||
// on this having a 0-tick delay.
|
||||
int slot = parseOptionalSlotNumber( args, 0, m_turtle.getSelectedSlot() );
|
||||
ItemStack stack = m_turtle.getInventory().getStackInSlot( slot );
|
||||
if( stack.isEmpty() ) return new Object[] { null };
|
||||
|
@@ -153,7 +153,7 @@ public class BlockTurtle extends BlockComputerBase<TileTurtle> implements IWater
|
||||
@Override
|
||||
public float getExplosionResistance( BlockState state, IWorldReader world, BlockPos pos, @Nullable Entity exploder, Explosion explosion )
|
||||
{
|
||||
if( getFamily() == ComputerFamily.Advanced && (exploder instanceof LivingEntity || exploder instanceof DamagingProjectileEntity) )
|
||||
if( getFamily() == ComputerFamily.Advanced || exploder instanceof LivingEntity || exploder instanceof DamagingProjectileEntity )
|
||||
{
|
||||
return 2000;
|
||||
}
|
||||
|
@@ -49,13 +49,12 @@ import net.minecraftforge.items.wrapper.InvWrapper;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
import javax.annotation.Nullable;
|
||||
import java.util.Collections;
|
||||
|
||||
import static net.minecraftforge.items.CapabilityItemHandler.ITEM_HANDLER_CAPABILITY;
|
||||
|
||||
public class TileTurtle extends TileComputerBase implements ITurtleTile, DefaultInventory
|
||||
{
|
||||
// Statics
|
||||
|
||||
public static final int INVENTORY_SIZE = 16;
|
||||
public static final int INVENTORY_WIDTH = 4;
|
||||
public static final int INVENTORY_HEIGHT = 4;
|
||||
@@ -70,8 +69,6 @@ public class TileTurtle extends TileComputerBase implements ITurtleTile, Default
|
||||
type -> new TileTurtle( type, ComputerFamily.Advanced )
|
||||
);
|
||||
|
||||
// Members
|
||||
|
||||
enum MoveState
|
||||
{
|
||||
NOT_MOVED,
|
||||
@@ -79,25 +76,20 @@ public class TileTurtle extends TileComputerBase implements ITurtleTile, Default
|
||||
MOVED
|
||||
}
|
||||
|
||||
private NonNullList<ItemStack> m_inventory;
|
||||
private NonNullList<ItemStack> m_previousInventory;
|
||||
private final NonNullList<ItemStack> m_inventory = NonNullList.withSize( INVENTORY_SIZE, ItemStack.EMPTY );
|
||||
private final NonNullList<ItemStack> m_previousInventory = NonNullList.withSize( INVENTORY_SIZE, ItemStack.EMPTY );
|
||||
private final IItemHandlerModifiable m_itemHandler = new InvWrapper( this );
|
||||
private LazyOptional<IItemHandlerModifiable> itemHandlerCap;
|
||||
private boolean m_inventoryChanged;
|
||||
private TurtleBrain m_brain;
|
||||
private MoveState m_moveState;
|
||||
private boolean m_inventoryChanged = false;
|
||||
private TurtleBrain m_brain = new TurtleBrain( this );
|
||||
private MoveState m_moveState = MoveState.NOT_MOVED;
|
||||
|
||||
public TileTurtle( TileEntityType<? extends TileGeneric> type, ComputerFamily family )
|
||||
{
|
||||
super( type, family );
|
||||
m_inventory = NonNullList.withSize( INVENTORY_SIZE, ItemStack.EMPTY );
|
||||
m_previousInventory = NonNullList.withSize( INVENTORY_SIZE, ItemStack.EMPTY );
|
||||
m_inventoryChanged = false;
|
||||
m_brain = new TurtleBrain( this );
|
||||
m_moveState = MoveState.NOT_MOVED;
|
||||
}
|
||||
|
||||
public boolean hasMoved()
|
||||
private boolean hasMoved()
|
||||
{
|
||||
return m_moveState == MoveState.MOVED;
|
||||
}
|
||||
@@ -237,18 +229,15 @@ public class TileTurtle extends TileComputerBase implements ITurtleTile, Default
|
||||
{
|
||||
super.tick();
|
||||
m_brain.update();
|
||||
synchronized( m_inventory )
|
||||
if( !getWorld().isRemote && m_inventoryChanged )
|
||||
{
|
||||
if( !getWorld().isRemote && m_inventoryChanged )
|
||||
{
|
||||
ServerComputer computer = getServerComputer();
|
||||
if( computer != null ) computer.queueEvent( "turtle_inventory" );
|
||||
ServerComputer computer = getServerComputer();
|
||||
if( computer != null ) computer.queueEvent( "turtle_inventory" );
|
||||
|
||||
m_inventoryChanged = false;
|
||||
for( int n = 0; n < getSizeInventory(); n++ )
|
||||
{
|
||||
m_previousInventory.set( n, InventoryUtil.copyItem( getStackInSlot( n ) ) );
|
||||
}
|
||||
m_inventoryChanged = false;
|
||||
for( int n = 0; n < getSizeInventory(); n++ )
|
||||
{
|
||||
m_previousInventory.set( n, getStackInSlot( n ).copy() );
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -288,8 +277,8 @@ public class TileTurtle extends TileComputerBase implements ITurtleTile, Default
|
||||
|
||||
// Read inventory
|
||||
ListNBT nbttaglist = nbt.getList( "Items", Constants.NBT.TAG_COMPOUND );
|
||||
m_inventory = NonNullList.withSize( INVENTORY_SIZE, ItemStack.EMPTY );
|
||||
m_previousInventory = NonNullList.withSize( INVENTORY_SIZE, ItemStack.EMPTY );
|
||||
m_inventory.clear();
|
||||
m_previousInventory.clear();
|
||||
for( int i = 0; i < nbttaglist.size(); i++ )
|
||||
{
|
||||
CompoundNBT tag = nbttaglist.getCompound( i );
|
||||
@@ -297,7 +286,7 @@ public class TileTurtle extends TileComputerBase implements ITurtleTile, Default
|
||||
if( slot < getSizeInventory() )
|
||||
{
|
||||
m_inventory.set( slot, ItemStack.read( tag ) );
|
||||
m_previousInventory.set( slot, InventoryUtil.copyItem( m_inventory.get( slot ) ) );
|
||||
m_previousInventory.set( slot, m_inventory.get( slot ).copy() );
|
||||
}
|
||||
}
|
||||
|
||||
@@ -396,7 +385,7 @@ public class TileTurtle extends TileComputerBase implements ITurtleTile, Default
|
||||
return m_brain.getToolRenderAngle( side, f );
|
||||
}
|
||||
|
||||
public void setOwningPlayer( GameProfile player )
|
||||
void setOwningPlayer( GameProfile player )
|
||||
{
|
||||
m_brain.setOwningPlayer( player );
|
||||
markDirty();
|
||||
@@ -424,109 +413,76 @@ public class TileTurtle extends TileComputerBase implements ITurtleTile, Default
|
||||
@Override
|
||||
public ItemStack getStackInSlot( int slot )
|
||||
{
|
||||
if( slot >= 0 && slot < INVENTORY_SIZE )
|
||||
{
|
||||
synchronized( m_inventory )
|
||||
{
|
||||
return m_inventory.get( slot );
|
||||
}
|
||||
}
|
||||
return ItemStack.EMPTY;
|
||||
return slot >= 0 && slot < INVENTORY_SIZE ? m_inventory.get( slot ) : ItemStack.EMPTY;
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
@Override
|
||||
public ItemStack removeStackFromSlot( int slot )
|
||||
{
|
||||
synchronized( m_inventory )
|
||||
{
|
||||
ItemStack result = getStackInSlot( slot );
|
||||
setInventorySlotContents( slot, ItemStack.EMPTY );
|
||||
return result;
|
||||
}
|
||||
ItemStack result = getStackInSlot( slot );
|
||||
setInventorySlotContents( slot, ItemStack.EMPTY );
|
||||
return result;
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
@Override
|
||||
public ItemStack decrStackSize( int slot, int count )
|
||||
{
|
||||
if( count == 0 )
|
||||
if( count == 0 ) return ItemStack.EMPTY;
|
||||
|
||||
ItemStack stack = getStackInSlot( slot );
|
||||
if( stack.isEmpty() ) return ItemStack.EMPTY;
|
||||
|
||||
if( stack.getCount() <= count )
|
||||
{
|
||||
return ItemStack.EMPTY;
|
||||
setInventorySlotContents( slot, ItemStack.EMPTY );
|
||||
return stack;
|
||||
}
|
||||
|
||||
synchronized( m_inventory )
|
||||
{
|
||||
ItemStack stack = getStackInSlot( slot );
|
||||
if( stack.isEmpty() )
|
||||
{
|
||||
return ItemStack.EMPTY;
|
||||
}
|
||||
|
||||
if( stack.getCount() <= count )
|
||||
{
|
||||
setInventorySlotContents( slot, ItemStack.EMPTY );
|
||||
return stack;
|
||||
}
|
||||
|
||||
ItemStack part = stack.split( count );
|
||||
onInventoryDefinitelyChanged();
|
||||
return part;
|
||||
}
|
||||
ItemStack part = stack.split( count );
|
||||
onInventoryDefinitelyChanged();
|
||||
return part;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setInventorySlotContents( int i, @Nonnull ItemStack stack )
|
||||
{
|
||||
if( i >= 0 && i < INVENTORY_SIZE )
|
||||
if( i >= 0 && i < INVENTORY_SIZE && !InventoryUtil.areItemsEqual( stack, m_inventory.get( i ) ) )
|
||||
{
|
||||
synchronized( m_inventory )
|
||||
{
|
||||
if( !InventoryUtil.areItemsEqual( stack, m_inventory.get( i ) ) )
|
||||
{
|
||||
m_inventory.set( i, stack );
|
||||
onInventoryDefinitelyChanged();
|
||||
}
|
||||
}
|
||||
m_inventory.set( i, stack );
|
||||
onInventoryDefinitelyChanged();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void clear()
|
||||
{
|
||||
synchronized( m_inventory )
|
||||
boolean changed = false;
|
||||
for( int i = 0; i < INVENTORY_SIZE; i++ )
|
||||
{
|
||||
boolean changed = false;
|
||||
for( int i = 0; i < INVENTORY_SIZE; i++ )
|
||||
if( !m_inventory.get( i ).isEmpty() )
|
||||
{
|
||||
if( !m_inventory.get( i ).isEmpty() )
|
||||
{
|
||||
m_inventory.set( i, ItemStack.EMPTY );
|
||||
changed = true;
|
||||
}
|
||||
}
|
||||
if( changed )
|
||||
{
|
||||
onInventoryDefinitelyChanged();
|
||||
m_inventory.set( i, ItemStack.EMPTY );
|
||||
changed = true;
|
||||
}
|
||||
}
|
||||
|
||||
if( changed ) onInventoryDefinitelyChanged();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void markDirty()
|
||||
{
|
||||
super.markDirty();
|
||||
synchronized( m_inventory )
|
||||
if( !m_inventoryChanged )
|
||||
{
|
||||
if( !m_inventoryChanged )
|
||||
for( int n = 0; n < getSizeInventory(); n++ )
|
||||
{
|
||||
for( int n = 0; n < getSizeInventory(); n++ )
|
||||
if( !ItemStack.areItemStacksEqual( getStackInSlot( n ), m_previousInventory.get( n ) ) )
|
||||
{
|
||||
if( !ItemStack.areItemStacksEqual( getStackInSlot( n ), m_previousInventory.get( n ) ) )
|
||||
{
|
||||
m_inventoryChanged = true;
|
||||
break;
|
||||
}
|
||||
m_inventoryChanged = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -563,7 +519,6 @@ public class TileTurtle extends TileComputerBase implements ITurtleTile, Default
|
||||
{
|
||||
super.readDescription( nbt );
|
||||
m_brain.readDescription( nbt );
|
||||
updateBlock();
|
||||
}
|
||||
|
||||
// Privates
|
||||
@@ -588,8 +543,8 @@ public class TileTurtle extends TileComputerBase implements ITurtleTile, Default
|
||||
public void transferStateFrom( TileTurtle copy )
|
||||
{
|
||||
super.transferStateFrom( copy );
|
||||
m_inventory = copy.m_inventory;
|
||||
m_previousInventory = copy.m_previousInventory;
|
||||
Collections.copy( m_inventory, copy.m_inventory );
|
||||
Collections.copy( m_previousInventory, copy.m_previousInventory );
|
||||
m_inventoryChanged = copy.m_inventoryChanged;
|
||||
m_brain = copy.m_brain;
|
||||
m_brain.setOwner( this );
|
||||
|
@@ -23,6 +23,7 @@ import dan200.computercraft.shared.turtle.blocks.TileTurtle;
|
||||
import dan200.computercraft.shared.util.Colour;
|
||||
import dan200.computercraft.shared.util.Holiday;
|
||||
import dan200.computercraft.shared.util.HolidayUtil;
|
||||
import dan200.computercraft.shared.util.InventoryDelegate;
|
||||
import net.minecraft.block.Block;
|
||||
import net.minecraft.block.BlockState;
|
||||
import net.minecraft.entity.Entity;
|
||||
@@ -43,6 +44,7 @@ import net.minecraft.util.math.Vec3d;
|
||||
import net.minecraft.world.World;
|
||||
import net.minecraftforge.common.util.Constants;
|
||||
import net.minecraftforge.items.IItemHandlerModifiable;
|
||||
import net.minecraftforge.items.wrapper.InvWrapper;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
import java.util.*;
|
||||
@@ -68,6 +70,9 @@ public class TurtleBrain implements ITurtleAccess
|
||||
private ComputerProxy m_proxy;
|
||||
private GameProfile m_owningPlayer;
|
||||
|
||||
private final IInventory m_inventory = (InventoryDelegate) () -> m_owner;
|
||||
private final IItemHandlerModifiable m_inventoryWrapper = new InvWrapper( m_inventory );
|
||||
|
||||
private Queue<TurtleCommandQueueEntry> m_commandQueue = new ArrayDeque<>();
|
||||
private int m_commandsIssued = 0;
|
||||
|
||||
@@ -439,14 +444,14 @@ public class TurtleBrain implements ITurtleAccess
|
||||
@Override
|
||||
public IInventory getInventory()
|
||||
{
|
||||
return m_owner;
|
||||
return m_inventory;
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
@Override
|
||||
public IItemHandlerModifiable getItemHandler()
|
||||
{
|
||||
return m_owner.getItemHandler();
|
||||
return m_inventoryWrapper;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@@ -14,8 +14,8 @@ import net.minecraft.block.BlockState;
|
||||
import net.minecraft.item.ItemStack;
|
||||
import net.minecraft.util.Direction;
|
||||
import net.minecraft.util.math.BlockPos;
|
||||
import net.minecraft.world.ServerWorld;
|
||||
import net.minecraft.world.World;
|
||||
import net.minecraft.world.server.ServerWorld;
|
||||
import net.minecraftforge.fml.common.ObfuscationReflectionHelper;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
|
@@ -17,7 +17,7 @@ import net.minecraft.item.ItemStack;
|
||||
import net.minecraft.util.Direction;
|
||||
import net.minecraft.util.math.BlockPos;
|
||||
import net.minecraft.util.math.Vec3d;
|
||||
import net.minecraft.world.ServerWorld;
|
||||
import net.minecraft.world.server.ServerWorld;
|
||||
import net.minecraftforge.common.util.FakePlayer;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
|
@@ -44,7 +44,7 @@ public class ContainerTurtle extends ContainerComputerBase
|
||||
super( TYPE, id, canUse, computer, family );
|
||||
this.properties = properties;
|
||||
|
||||
func_216961_a( properties );
|
||||
trackIntArray( properties );
|
||||
|
||||
// Turtle inventory
|
||||
for( int y = 0; y < 4; y++ )
|
||||
|
@@ -15,8 +15,8 @@ import net.minecraft.item.ItemStack;
|
||||
import net.minecraft.item.crafting.IRecipe;
|
||||
import net.minecraft.item.crafting.IRecipeType;
|
||||
import net.minecraft.util.NonNullList;
|
||||
import net.minecraft.world.ServerWorld;
|
||||
import net.minecraft.world.World;
|
||||
import net.minecraft.world.server.ServerWorld;
|
||||
import net.minecraftforge.common.ForgeHooks;
|
||||
import net.minecraftforge.fml.hooks.BasicEventHooks;
|
||||
|
||||
|
@@ -248,7 +248,7 @@ public class TurtleTool extends AbstractTurtleUpgrade
|
||||
boolean canHarvest = state.canHarvestBlock( world, blockPosition, turtlePlayer );
|
||||
boolean canBreak = state.removedByPlayer( world, blockPosition, turtlePlayer, canHarvest, fluidState );
|
||||
if( canBreak ) state.getBlock().onPlayerDestroy( world, blockPosition, state );
|
||||
if( canHarvest )
|
||||
if( canHarvest && canBreak )
|
||||
{
|
||||
state.getBlock().harvestBlock( world, turtlePlayer, blockPosition, state, tile, turtlePlayer.getHeldItemMainhand() );
|
||||
}
|
||||
|
@@ -14,8 +14,6 @@ import net.minecraft.util.math.AxisAlignedBB;
|
||||
import net.minecraft.util.math.BlockPos;
|
||||
import net.minecraft.world.World;
|
||||
import net.minecraftforge.event.entity.EntityJoinWorldEvent;
|
||||
import net.minecraftforge.event.entity.living.LivingDropsEvent;
|
||||
import net.minecraftforge.event.world.BlockEvent;
|
||||
import net.minecraftforge.eventbus.api.EventPriority;
|
||||
import net.minecraftforge.eventbus.api.SubscribeEvent;
|
||||
import net.minecraftforge.fml.common.Mod;
|
||||
@@ -36,7 +34,6 @@ public final class DropConsumer
|
||||
private static Function<ItemStack, ItemStack> dropConsumer;
|
||||
private static List<ItemStack> remainingDrops;
|
||||
private static WeakReference<World> dropWorld;
|
||||
private static BlockPos dropPos;
|
||||
private static AxisAlignedBB dropBounds;
|
||||
private static WeakReference<Entity> dropEntity;
|
||||
|
||||
@@ -46,7 +43,6 @@ public final class DropConsumer
|
||||
remainingDrops = new ArrayList<>();
|
||||
dropEntity = new WeakReference<>( entity );
|
||||
dropWorld = new WeakReference<>( entity.world );
|
||||
dropPos = null;
|
||||
dropBounds = new AxisAlignedBB( entity.getPosition() ).grow( 2, 2, 2 );
|
||||
|
||||
entity.captureDrops( new ArrayList<>() );
|
||||
@@ -55,10 +51,9 @@ public final class DropConsumer
|
||||
public static void set( World world, BlockPos pos, Function<ItemStack, ItemStack> consumer )
|
||||
{
|
||||
dropConsumer = consumer;
|
||||
remainingDrops = new ArrayList<>();
|
||||
remainingDrops = new ArrayList<>( 2 );
|
||||
dropEntity = null;
|
||||
dropWorld = new WeakReference<>( world );
|
||||
dropPos = pos;
|
||||
dropBounds = new AxisAlignedBB( pos ).grow( 2, 2, 2 );
|
||||
}
|
||||
|
||||
@@ -83,7 +78,6 @@ public final class DropConsumer
|
||||
remainingDrops = null;
|
||||
dropEntity = null;
|
||||
dropWorld = null;
|
||||
dropPos = null;
|
||||
dropBounds = null;
|
||||
|
||||
return remainingStacks;
|
||||
@@ -95,34 +89,7 @@ public final class DropConsumer
|
||||
if( !remaining.isEmpty() ) remainingDrops.add( remaining );
|
||||
}
|
||||
|
||||
@SubscribeEvent( priority = EventPriority.LOWEST )
|
||||
public static void onEntityLivingDrops( LivingDropsEvent event )
|
||||
{
|
||||
// Capture any mob drops for the current entity
|
||||
if( dropEntity != null && event.getEntity() == dropEntity.get() )
|
||||
{
|
||||
Collection<ItemEntity> drops = event.getDrops();
|
||||
for( ItemEntity entityItem : drops ) handleDrops( entityItem.getItem() );
|
||||
drops.clear();
|
||||
}
|
||||
}
|
||||
|
||||
@SubscribeEvent( priority = EventPriority.LOWEST )
|
||||
public static void onHarvestDrops( BlockEvent.HarvestDropsEvent event )
|
||||
{
|
||||
// Capture block drops for the current entity
|
||||
if( dropWorld != null && dropWorld.get() == event.getWorld()
|
||||
&& dropPos != null && dropPos.equals( event.getPos() ) )
|
||||
{
|
||||
for( ItemStack item : event.getDrops() )
|
||||
{
|
||||
if( event.getWorld().getRandom().nextFloat() < event.getDropChance() ) handleDrops( item );
|
||||
}
|
||||
event.getDrops().clear();
|
||||
}
|
||||
}
|
||||
|
||||
@SubscribeEvent( priority = EventPriority.LOWEST )
|
||||
@SubscribeEvent( priority = EventPriority.HIGHEST )
|
||||
public static void onEntitySpawn( EntityJoinWorldEvent event )
|
||||
{
|
||||
// Capture any nearby item spawns
|
||||
|
@@ -0,0 +1,120 @@
|
||||
/*
|
||||
* This file is part of ComputerCraft - http://www.computercraft.info
|
||||
* Copyright Daniel Ratcliffe, 2011-2019. Do not distribute without permission.
|
||||
* Send enquiries to dratcliffe@gmail.com
|
||||
*/
|
||||
|
||||
package dan200.computercraft.shared.util;
|
||||
|
||||
import net.minecraft.entity.player.PlayerEntity;
|
||||
import net.minecraft.inventory.IInventory;
|
||||
import net.minecraft.item.Item;
|
||||
import net.minecraft.item.ItemStack;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* Provides a delegate over inventories.
|
||||
*
|
||||
* This may be used both on {@link net.minecraft.tileentity.TileEntity}s to redirect the inventory to another tile,
|
||||
* and by other interfaces to have inventories which change their backing store.
|
||||
*/
|
||||
@FunctionalInterface
|
||||
public interface InventoryDelegate extends IInventory
|
||||
{
|
||||
IInventory getInventory();
|
||||
|
||||
@Override
|
||||
default int getSizeInventory()
|
||||
{
|
||||
return getInventory().getSizeInventory();
|
||||
}
|
||||
|
||||
@Override
|
||||
default boolean isEmpty()
|
||||
{
|
||||
return getInventory().isEmpty();
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
@Override
|
||||
default ItemStack getStackInSlot( int slot )
|
||||
{
|
||||
return getInventory().getStackInSlot( slot );
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
@Override
|
||||
default ItemStack decrStackSize( int slot, int count )
|
||||
{
|
||||
return getInventory().decrStackSize( slot, count );
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
@Override
|
||||
default ItemStack removeStackFromSlot( int slot )
|
||||
{
|
||||
return getInventory().removeStackFromSlot( slot );
|
||||
}
|
||||
|
||||
@Override
|
||||
default void setInventorySlotContents( int slot, ItemStack stack )
|
||||
{
|
||||
getInventory().setInventorySlotContents( slot, stack );
|
||||
}
|
||||
|
||||
@Override
|
||||
default int getInventoryStackLimit()
|
||||
{
|
||||
return getInventory().getInventoryStackLimit();
|
||||
}
|
||||
|
||||
@Override
|
||||
default void markDirty()
|
||||
{
|
||||
getInventory().markDirty();
|
||||
}
|
||||
|
||||
@Override
|
||||
default boolean isUsableByPlayer( @Nonnull PlayerEntity player )
|
||||
{
|
||||
return getInventory().isUsableByPlayer( player );
|
||||
}
|
||||
|
||||
@Override
|
||||
default void openInventory( @Nonnull PlayerEntity player )
|
||||
{
|
||||
getInventory().openInventory( player );
|
||||
}
|
||||
|
||||
@Override
|
||||
default void closeInventory( @Nonnull PlayerEntity player )
|
||||
{
|
||||
getInventory().closeInventory( player );
|
||||
}
|
||||
|
||||
@Override
|
||||
default boolean isItemValidForSlot( int slot, @Nonnull ItemStack stack )
|
||||
{
|
||||
return getInventory().isItemValidForSlot( slot, stack );
|
||||
}
|
||||
|
||||
@Override
|
||||
default void clear()
|
||||
{
|
||||
getInventory().clear();
|
||||
}
|
||||
|
||||
@Override
|
||||
default int count( @Nonnull Item stack )
|
||||
{
|
||||
return getInventory().count( stack );
|
||||
}
|
||||
|
||||
@Override
|
||||
default boolean hasAny( @Nonnull Set<Item> set )
|
||||
{
|
||||
return getInventory().hasAny( set );
|
||||
}
|
||||
}
|
@@ -70,12 +70,6 @@ public final class InventoryUtil
|
||||
return shareTagA.equals( shareTagB );
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
public static ItemStack copyItem( @Nonnull ItemStack a )
|
||||
{
|
||||
return a.copy();
|
||||
}
|
||||
|
||||
// Methods for finding inventories:
|
||||
|
||||
public static IItemHandler getInventory( World world, BlockPos pos, Direction side )
|
||||
|
@@ -6,16 +6,10 @@
|
||||
|
||||
package dan200.computercraft.shared.util;
|
||||
|
||||
import com.mojang.datafixers.DataFixUtils;
|
||||
import com.mojang.datafixers.types.Type;
|
||||
import dan200.computercraft.ComputerCraft;
|
||||
import net.minecraft.block.Block;
|
||||
import net.minecraft.tileentity.TileEntity;
|
||||
import net.minecraft.tileentity.TileEntityType;
|
||||
import net.minecraft.util.ResourceLocation;
|
||||
import net.minecraft.util.SharedConstants;
|
||||
import net.minecraft.util.datafix.DataFixesManager;
|
||||
import net.minecraft.util.datafix.TypeReferences;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
import java.util.Collections;
|
||||
@@ -30,7 +24,7 @@ public final class NamedTileEntityType<T extends TileEntity> extends TileEntityT
|
||||
|
||||
private NamedTileEntityType( ResourceLocation identifier, Supplier<? extends T> supplier )
|
||||
{
|
||||
super( supplier, Collections.emptySet(), getDatafixer( identifier ) );
|
||||
super( supplier, Collections.emptySet(), null );
|
||||
this.identifier = identifier;
|
||||
setRegistryName( identifier );
|
||||
}
|
||||
@@ -62,22 +56,6 @@ public final class NamedTileEntityType<T extends TileEntity> extends TileEntityT
|
||||
return identifier;
|
||||
}
|
||||
|
||||
private static Type<?> getDatafixer( ResourceLocation id )
|
||||
{
|
||||
try
|
||||
{
|
||||
return DataFixesManager.getDataFixer()
|
||||
.getSchema( DataFixUtils.makeKey( ComputerCraft.DATAFIXER_VERSION ) )
|
||||
.getChoiceType( TypeReferences.BLOCK_ENTITY, id.toString() );
|
||||
}
|
||||
catch( IllegalArgumentException e )
|
||||
{
|
||||
if( SharedConstants.developmentMode ) throw e;
|
||||
ComputerCraft.log.warn( "No data fixer registered for block entity " + id );
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private static final class FixedPointSupplier<T extends TileEntity> implements Supplier<T>
|
||||
{
|
||||
final NamedTileEntityType<T> factory;
|
||||
|
@@ -9,17 +9,11 @@ package dan200.computercraft.shared.util;
|
||||
import dan200.computercraft.shared.network.NetworkHandler;
|
||||
import dan200.computercraft.shared.network.NetworkMessage;
|
||||
import dan200.computercraft.shared.network.client.PlayRecordClientMessage;
|
||||
import net.minecraft.item.Item;
|
||||
import net.minecraft.item.ItemStack;
|
||||
import net.minecraft.item.MusicDiscItem;
|
||||
import net.minecraft.util.SoundEvent;
|
||||
import net.minecraft.util.math.BlockPos;
|
||||
import net.minecraft.util.math.Vec3d;
|
||||
import net.minecraft.util.text.TranslationTextComponent;
|
||||
import net.minecraft.world.World;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
|
||||
public final class RecordUtil
|
||||
{
|
||||
private RecordUtil() {}
|
||||
@@ -29,12 +23,4 @@ public final class RecordUtil
|
||||
NetworkMessage packet = record != null ? new PlayRecordClientMessage( pos, record, recordInfo ) : new PlayRecordClientMessage( pos );
|
||||
NetworkHandler.sendToAllAround( packet, world, new Vec3d( pos ), 64 );
|
||||
}
|
||||
|
||||
public static String getRecordInfo( @Nonnull ItemStack recordStack )
|
||||
{
|
||||
Item item = recordStack.getItem();
|
||||
if( !(item instanceof MusicDiscItem) ) return null;
|
||||
|
||||
return new TranslationTextComponent( item.getTranslationKey() + ".desc" ).getString();
|
||||
}
|
||||
}
|
||||
|
@@ -13,9 +13,9 @@ import net.minecraft.tileentity.TileEntity;
|
||||
import net.minecraft.util.math.BlockPos;
|
||||
import net.minecraft.world.ITickList;
|
||||
import net.minecraft.world.World;
|
||||
import net.minecraftforge.event.TickEvent;
|
||||
import net.minecraftforge.eventbus.api.SubscribeEvent;
|
||||
import net.minecraftforge.fml.common.Mod;
|
||||
import net.minecraftforge.fml.common.gameevent.TickEvent;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.Iterator;
|
||||
|
@@ -7,6 +7,7 @@
|
||||
package dan200.computercraft.shared.util;
|
||||
|
||||
import com.google.common.base.Predicate;
|
||||
import com.google.common.collect.MapMaker;
|
||||
import net.minecraft.block.BlockState;
|
||||
import net.minecraft.entity.*;
|
||||
import net.minecraft.entity.item.ItemEntity;
|
||||
@@ -20,25 +21,35 @@ import org.apache.commons.lang3.tuple.Pair;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
public final class WorldUtil
|
||||
{
|
||||
@SuppressWarnings( "Guava" )
|
||||
private static final Predicate<Entity> CAN_COLLIDE = x -> x != null && x.isAlive() && x.canBeCollidedWith();
|
||||
|
||||
private static final Entity ENTITY = new ItemEntity( EntityType.ITEM, null )
|
||||
{
|
||||
@Override
|
||||
public EntitySize getSize( Pose pose )
|
||||
{
|
||||
return EntitySize.fixed( 0, 0 );
|
||||
}
|
||||
};
|
||||
private static final Map<World, Entity> entityCache = new MapMaker().weakKeys().weakValues().makeMap();
|
||||
|
||||
static
|
||||
private static synchronized Entity getEntity( World world )
|
||||
{
|
||||
ENTITY.noClip = true;
|
||||
ENTITY.recalculateSize();
|
||||
// TODO: It'd be nice if we could avoid this. Maybe always use the turtle player (if it's available).
|
||||
Entity entity = entityCache.get( world );
|
||||
if( entity != null ) return entity;
|
||||
|
||||
entity = new ItemEntity( EntityType.ITEM, world )
|
||||
{
|
||||
@Nonnull
|
||||
@Override
|
||||
public EntitySize getSize( Pose pose )
|
||||
{
|
||||
return EntitySize.fixed( 0, 0 );
|
||||
}
|
||||
};
|
||||
|
||||
entity.noClip = true;
|
||||
entity.recalculateSize();
|
||||
entityCache.put( world, entity );
|
||||
return entity;
|
||||
}
|
||||
|
||||
public static boolean isLiquidBlock( World world, BlockPos pos )
|
||||
@@ -61,8 +72,9 @@ public final class WorldUtil
|
||||
Vec3d vecEnd = vecStart.add( vecDir.x * distance, vecDir.y * distance, vecDir.z * distance );
|
||||
|
||||
// Raycast for blocks
|
||||
ENTITY.setPosition( vecStart.x, vecStart.y, vecStart.z );
|
||||
RayTraceContext context = new RayTraceContext( vecStart, vecEnd, RayTraceContext.BlockMode.COLLIDER, RayTraceContext.FluidMode.NONE, ENTITY );
|
||||
Entity collisionEntity = getEntity( world );
|
||||
collisionEntity.setPosition( vecStart.x, vecStart.y, vecStart.z );
|
||||
RayTraceContext context = new RayTraceContext( vecStart, vecEnd, RayTraceContext.BlockMode.COLLIDER, RayTraceContext.FluidMode.NONE, collisionEntity );
|
||||
RayTraceResult result = world.rayTraceBlocks( context );
|
||||
if( result != null && result.getType() == RayTraceResult.Type.BLOCK )
|
||||
{
|
||||
|
@@ -1,5 +1,5 @@
|
||||
modLoader="javafml"
|
||||
loaderVersion="[27,28)"
|
||||
loaderVersion="[28,29)"
|
||||
|
||||
issueTrackerURL="https://github.com/SquidDev-CC/CC-Tweaked/issues"
|
||||
displayURL="https://github.com/SquidDev-CC/CC-Tweaked"
|
||||
@@ -19,6 +19,6 @@ CC: Tweaked is a fork of ComputerCraft, adding programmable computers, turtles a
|
||||
[[dependencies.computercraft]]
|
||||
modId="forge"
|
||||
mandatory=true
|
||||
versionRange="[27,28)"
|
||||
versionRange="[28,29)"
|
||||
ordering="NONE"
|
||||
side="BOTH"
|
||||
|
@@ -1,48 +1,19 @@
|
||||
local native_select, native_type = select, type
|
||||
|
||||
--- Expect an argument to have a specific type.
|
||||
-- Load in expect from the module path.
|
||||
--
|
||||
-- @tparam int index The 1-based argument index.
|
||||
-- @param value The argument's value.
|
||||
-- @tparam string ... The allowed types of the argument.
|
||||
-- @throws If the value is not one of the allowed types.
|
||||
local function expect(index, value, ...)
|
||||
local t = native_type(value)
|
||||
for i = 1, native_select("#", ...) do
|
||||
if t == native_select(i, ...) then return true end
|
||||
end
|
||||
-- Ideally we'd use require, but that is part of the shell, and so is not
|
||||
-- available to the BIOS or any APIs. All APIs load this using dofile, but that
|
||||
-- has not been defined at this point.
|
||||
local expect
|
||||
|
||||
local types = table.pack(...)
|
||||
for i = types.n, 1, -1 do
|
||||
if types[i] == "nil" then table.remove(types, i) end
|
||||
end
|
||||
do
|
||||
local h = fs.open("rom/modules/main/cc/expect.lua", "r")
|
||||
local f, err = loadstring(h.readAll(), "@expect.lua")
|
||||
h.close()
|
||||
|
||||
local type_names
|
||||
if #types <= 1 then
|
||||
type_names = tostring(...)
|
||||
else
|
||||
type_names = table.concat(types, ", ", 1, #types - 1) .. " or " .. types[#types]
|
||||
end
|
||||
|
||||
-- If we can determine the function name with a high level of confidence, try to include it.
|
||||
local name
|
||||
if native_type(debug) == "table" and native_type(debug.getinfo) == "function" then
|
||||
local ok, info = pcall(debug.getinfo, 3, "nS")
|
||||
if ok and info.name and #info.name ~= "" and info.what ~= "C" then name = info.name end
|
||||
end
|
||||
|
||||
if name then
|
||||
error( ("bad argument #%d to '%s' (expected %s, got %s)"):format(index, name, type_names, t), 3 )
|
||||
else
|
||||
error( ("bad argument #%d (expected %s, got %s)"):format(index, type_names, t), 3 )
|
||||
end
|
||||
if not f then error(err) end
|
||||
expect = f().expect
|
||||
end
|
||||
|
||||
-- We expose expect in the global table as APIs need to access it, but give it
|
||||
-- a non-identifier name - meaning it does not show up in auto-completion.
|
||||
-- expect is an internal function, and should not be used by users.
|
||||
_G["~expect"] = expect
|
||||
|
||||
if _VERSION == "Lua 5.1" then
|
||||
-- If we're on Lua 5.1, install parts of the Lua 5.2/5.3 API so that programs can be written against it
|
||||
local type = type
|
||||
@@ -568,23 +539,28 @@ function read( _sReplaceChar, _tHistory, _fnComplete, _sDefault )
|
||||
return sLine
|
||||
end
|
||||
|
||||
function loadfile( _sFile, _tEnv )
|
||||
expect(1, _sFile, "string")
|
||||
expect(2, _tEnv, "table", "nil")
|
||||
|
||||
local file = fs.open( _sFile, "r" )
|
||||
if file then
|
||||
local func, err = load( file.readAll(), "@" .. fs.getName( _sFile ), "t", _tEnv )
|
||||
file.close()
|
||||
return func, err
|
||||
function loadfile( filename, mode, env )
|
||||
-- Support the previous `loadfile(filename, env)` form instead.
|
||||
if type(mode) == "table" and env == nil then
|
||||
mode, env = nil, mode
|
||||
end
|
||||
return nil, "File not found"
|
||||
|
||||
expect(1, filename, "string")
|
||||
expect(2, mode, "string", "nil")
|
||||
expect(3, env, "table", "nil")
|
||||
|
||||
local file = fs.open( filename, "r" )
|
||||
if not file then return nil, "File not found" end
|
||||
|
||||
local func, err = load( file.readAll(), "@" .. fs.getName( filename ), mode, env )
|
||||
file.close()
|
||||
return func, err
|
||||
end
|
||||
|
||||
function dofile( _sFile )
|
||||
expect(1, _sFile, "string")
|
||||
|
||||
local fnFile, e = loadfile( _sFile, _G )
|
||||
local fnFile, e = loadfile( _sFile, nil, _G )
|
||||
if fnFile then
|
||||
return fnFile()
|
||||
else
|
||||
@@ -600,7 +576,7 @@ function os.run( _tEnv, _sPath, ... )
|
||||
local tArgs = table.pack( ... )
|
||||
local tEnv = _tEnv
|
||||
setmetatable( tEnv, { __index = _G } )
|
||||
local fnFile, err = loadfile( _sPath, tEnv )
|
||||
local fnFile, err = loadfile( _sPath, nil, tEnv )
|
||||
if fnFile then
|
||||
local ok, err = pcall( function()
|
||||
fnFile( table.unpack( tArgs, 1, tArgs.n ) )
|
||||
@@ -634,7 +610,7 @@ function os.loadAPI( _sPath )
|
||||
|
||||
local tEnv = {}
|
||||
setmetatable( tEnv, { __index = _G } )
|
||||
local fnAPI, err = loadfile( _sPath, tEnv )
|
||||
local fnAPI, err = loadfile( _sPath, nil, tEnv )
|
||||
if fnAPI then
|
||||
local ok, err = pcall( fnAPI )
|
||||
if not ok then
|
||||
|
@@ -1,4 +1,4 @@
|
||||
local expect = _G["~expect"]
|
||||
local expect = dofile("rom/modules/main/cc/expect.lua").expect
|
||||
|
||||
-- Colors
|
||||
white = 1
|
||||
|
@@ -1,4 +1,4 @@
|
||||
local expect = _G["~expect"]
|
||||
local expect = dofile("rom/modules/main/cc/expect.lua").expect
|
||||
|
||||
CHANNEL_GPS = 65534
|
||||
|
||||
|
@@ -1,4 +1,4 @@
|
||||
local expect = _G["~expect"]
|
||||
local expect = dofile("rom/modules/main/cc/expect.lua").expect
|
||||
|
||||
local sPath = "/rom/help"
|
||||
|
||||
|
@@ -1,6 +1,6 @@
|
||||
-- Definition for the IO API
|
||||
|
||||
local expect, typeOf = _G["~expect"], _G.type
|
||||
local expect, typeOf = dofile("rom/modules/main/cc/expect.lua").expect, _G.type
|
||||
|
||||
--- If we return nil then close the file, as we've reached the end.
|
||||
-- We use this weird wrapper function as we wish to preserve the varargs
|
||||
|
@@ -8,7 +8,7 @@
|
||||
-- taught me anything, it's that emulating LWJGL's weird key handling is nigh-on
|
||||
-- impossible.
|
||||
|
||||
local expect = _G["~expect"]
|
||||
local expect = dofile("rom/modules/main/cc/expect.lua").expect
|
||||
|
||||
local tKeys = {}
|
||||
tKeys[32] = 'space'
|
||||
|
@@ -1,4 +1,4 @@
|
||||
local expect = _G["~expect"]
|
||||
local expect = dofile("rom/modules/main/cc/expect.lua").expect
|
||||
|
||||
local function drawPixelInternal( xPos, yPos )
|
||||
term.setCursorPos( xPos, yPos )
|
||||
|
@@ -1,4 +1,4 @@
|
||||
local expect = _G["~expect"]
|
||||
local expect = dofile("rom/modules/main/cc/expect.lua").expect
|
||||
|
||||
local native = peripheral
|
||||
|
||||
|
@@ -1,4 +1,4 @@
|
||||
local expect = _G["~expect"]
|
||||
local expect = dofile("rom/modules/main/cc/expect.lua").expect
|
||||
|
||||
CHANNEL_BROADCAST = 65535
|
||||
CHANNEL_REPEAT = 65533
|
||||
|
@@ -1,4 +1,4 @@
|
||||
local expect = _G["~expect"]
|
||||
local expect = dofile("rom/modules/main/cc/expect.lua").expect
|
||||
|
||||
local tSettings = {}
|
||||
|
||||
|
@@ -1,4 +1,4 @@
|
||||
local expect = _G["~expect"]
|
||||
local expect = dofile("rom/modules/main/cc/expect.lua").expect
|
||||
|
||||
local native = (term.native and term.native()) or term
|
||||
local redirectTarget = native
|
||||
|
@@ -1,4 +1,4 @@
|
||||
local expect = _G["~expect"]
|
||||
local expect = dofile("rom/modules/main/cc/expect.lua").expect
|
||||
|
||||
function slowWrite( sText, nRate )
|
||||
expect(2, nRate, "number", "nil")
|
||||
|
@@ -1,4 +1,4 @@
|
||||
local expect = _G["~expect"]
|
||||
local expect = dofile("rom/modules/main/cc/expect.lua").expect
|
||||
|
||||
local tHex = {
|
||||
[ colors.white ] = "0",
|
||||
@@ -388,6 +388,16 @@ function create( parent, nX, nY, nWidth, nHeight, bStartVisible )
|
||||
return nBackgroundColor
|
||||
end
|
||||
|
||||
function window.getLine(y)
|
||||
if type(y) ~= "number" then expect(1, y, "number") end
|
||||
|
||||
if y < 1 or y > nHeight then
|
||||
error("Line is out of range.", 2)
|
||||
end
|
||||
|
||||
return tLines[y].text, tLines[y].textColor, tLines[y].backgroundColor
|
||||
end
|
||||
|
||||
-- Other functions
|
||||
function window.setVisible( bVis )
|
||||
if type(bVis) ~= "boolean" then expect(1, bVis, "boolean") end
|
||||
@@ -421,16 +431,20 @@ function create( parent, nX, nY, nWidth, nHeight, bStartVisible )
|
||||
return nX, nY
|
||||
end
|
||||
|
||||
function window.reposition( nNewX, nNewY, nNewWidth, nNewHeight )
|
||||
function window.reposition( nNewX, nNewY, nNewWidth, nNewHeight, newParent )
|
||||
if type(nNewX) ~= "number" then expect(1, nNewX, "number") end
|
||||
if type(nNewY) ~= "number" then expect(2, nNewY, "number") end
|
||||
if nNewWidth ~= nil or nNewHeight ~= nil then
|
||||
expect(3, nNewWidth, "number")
|
||||
expect(4, nNewHeight, "number")
|
||||
end
|
||||
if newParent ~= nil and type(newParent) ~= "table" then expect(5, newParent, "table") end
|
||||
|
||||
nX = nNewX
|
||||
nY = nNewY
|
||||
|
||||
if newParent then parent = newParent end
|
||||
|
||||
if nNewWidth and nNewHeight then
|
||||
local tNewLines = {}
|
||||
createEmptyLines( nNewWidth )
|
||||
|
@@ -1,3 +1,37 @@
|
||||
# New features in CC: Tweaked 1.85.0
|
||||
|
||||
* Window.reposition now allows changing the redirect buffer
|
||||
* Add cc.completion and cc.shell.completion modules
|
||||
* command.exec also returns the number of affected objects, when exposed by the game.
|
||||
|
||||
And several bug fixes:
|
||||
* Change how turtle mining drops are handled, improving compatibility with some mods.
|
||||
* Fix several GUI desyncs after a turtle moves.
|
||||
* Fix os.day/os.time using the incorrect world time.
|
||||
* Prevent wired modems dropping incorrectly.
|
||||
* Fix mouse events not firing within the computer GUI.
|
||||
|
||||
# New features in CC: Tweaked 1.84.1
|
||||
|
||||
* Update to latest Forge
|
||||
|
||||
# New features in CC: Tweaked 1.84.0
|
||||
|
||||
* Improve validation in rename, copy and delete programs
|
||||
* Add window.getLine - the inverse of blit
|
||||
* turtle.refuel no longer consumes more fuel than needed
|
||||
* Add "cc.expect" module, for improved argument type checks
|
||||
* Mount the ROM from all mod jars, not just CC's
|
||||
|
||||
And several bug fixes:
|
||||
* Ensure file error messages use the absolute correct path
|
||||
* Fix NPE when closing a file multiple times.
|
||||
* Do not load chunks when calling writeDescription.
|
||||
* Fix the signature of loadfile
|
||||
* Fix turtles harvesting blocks multiple times
|
||||
* Improve thread-safety of various peripherals
|
||||
* Prevent printed pages having massive/malformed titles
|
||||
|
||||
# New features in CC: Tweaked 1.83.1
|
||||
|
||||
* Add several new MOTD messages (JakobDev)
|
||||
|
@@ -1,10 +1,14 @@
|
||||
New features in CC: Tweaked 1.83.1
|
||||
New features in CC: Tweaked 1.85.0
|
||||
|
||||
* Add several new MOTD messages (JakobDev)
|
||||
* Window.reposition now allows changing the redirect buffer
|
||||
* Add cc.completion and cc.shell.completion modules
|
||||
* command.exec also returns the number of affected objects, when exposed by the game.
|
||||
|
||||
And several bug fixes:
|
||||
* Fix type check in `rednet.lookup`
|
||||
* Error if turtle and pocket computer programs are run on the wrong system (JakobDev)
|
||||
* Do not discard varargs after a nil.
|
||||
* Change how turtle mining drops are handled, improving compatibility with some mods.
|
||||
* Fix several GUI desyncs after a turtle moves.
|
||||
* Fix os.day/os.time using the incorrect world time.
|
||||
* Prevent wired modems dropping incorrectly.
|
||||
* Fix mouse events not firing within the computer GUI.
|
||||
|
||||
Type "help changelog" to see the full version history.
|
||||
|
@@ -23,3 +23,4 @@ getPosition()
|
||||
reposition( x, y, width, height )
|
||||
getPaletteColor( color )
|
||||
setPaletteColor( color, r, g, b )
|
||||
getLine()
|
||||
|
@@ -0,0 +1,105 @@
|
||||
--- A collection of helper methods for working with input completion, such
|
||||
-- as that require by @{read}.
|
||||
--
|
||||
-- @module craftos.completion
|
||||
-- @see cc.shell.completion For additional helpers to use with
|
||||
-- @{shell.setCompletionFunction}.
|
||||
|
||||
local expect = require "cc.expect".expect
|
||||
|
||||
local function choice_impl(text, choices, add_space)
|
||||
local results = {}
|
||||
for n = 1, #choices do
|
||||
local option = choices[n]
|
||||
if #option + (add_space and 1 or 0) > #text and option:sub(1, #text) == text then
|
||||
local result = option:sub(#text + 1)
|
||||
if add_space then
|
||||
table.insert(results, result .. " ")
|
||||
else
|
||||
table.insert(results, result)
|
||||
end
|
||||
end
|
||||
end
|
||||
return results
|
||||
end
|
||||
|
||||
--- Complete from a choice of one or more strings.
|
||||
--
|
||||
-- @tparam string text The input string to complete.
|
||||
-- @tparam { string... } choices The list of choices to complete from.
|
||||
-- @tparam[opt] boolean add_space Whether to add a space after the completed item.
|
||||
-- @treturn { string... } A list of suffixes of matching strings.
|
||||
-- @usage Call @{read}, completing the names of various animals.
|
||||
--
|
||||
-- local animals = { "dog", "cat", "lion", "unicorn" }
|
||||
-- read(nil, nil, function(text) return choice(text, animals) end)
|
||||
local function choice(text, choices, add_space)
|
||||
expect(1, text, "string")
|
||||
expect(2, choices, "table")
|
||||
expect(3, add_space, "boolean", "nil")
|
||||
return choice_impl(text, choices, add_space)
|
||||
end
|
||||
|
||||
--- Complete the name of a currently attached peripheral.
|
||||
--
|
||||
-- @tparam string text The input string to complete.
|
||||
-- @tparam[opt] boolean add_space Whether to add a space after the completed name.
|
||||
-- @treturn { string... } A list of suffixes of matching peripherals.
|
||||
-- @usage read(nil, nil, peripheral)
|
||||
local function peripheral_(text, add_space)
|
||||
expect(1, text, "string")
|
||||
expect(2, add_space, "boolean", "nil")
|
||||
return choice_impl(text, peripheral.getNames(), add_space)
|
||||
end
|
||||
|
||||
local sides = redstone.getSides()
|
||||
|
||||
--- Complete the side of a computer.
|
||||
--
|
||||
-- @tparam string text The input string to complete.
|
||||
-- @tparam[opt] boolean add_space Whether to add a space after the completed side.
|
||||
-- @treturn { string... } A list of suffixes of matching sides.
|
||||
-- @usage read(nil, nil, side)
|
||||
local function side(text, add_space)
|
||||
expect(1, text, "string")
|
||||
expect(2, add_space, "boolean", "nil")
|
||||
return choice_impl(text, sides, add_space)
|
||||
end
|
||||
|
||||
--- Complete a @{settings|setting}.
|
||||
--
|
||||
-- @tparam string text The input string to complete.
|
||||
-- @tparam[opt] boolean add_space Whether to add a space after the completed settings.
|
||||
-- @treturn { string... } A list of suffixes of matching settings.
|
||||
-- @usage read(nil, nil, setting)
|
||||
local function setting(text, add_space)
|
||||
expect(1, text, "string")
|
||||
expect(2, add_space, "boolean", "nil")
|
||||
return choice_impl(text, settings.getNames(), add_space)
|
||||
end
|
||||
|
||||
local command_list
|
||||
|
||||
--- Complete the name of a Minecraft @{commands|command}.
|
||||
--
|
||||
-- @tparam string text The input string to complete.
|
||||
-- @tparam[opt] boolean add_space Whether to add a space after the completed command.
|
||||
-- @treturn { string... } A list of suffixes of matching commands.
|
||||
-- @usage read(nil, nil, command)
|
||||
local function command(text, add_space)
|
||||
expect(1, text, "string")
|
||||
expect(2, add_space, "boolean", "nil")
|
||||
if command_list == nil then
|
||||
command_list = commands and commands.list() or {}
|
||||
end
|
||||
|
||||
return choice_impl(text, command_list, add_space)
|
||||
end
|
||||
|
||||
return {
|
||||
choice = choice,
|
||||
peripheral = peripheral_,
|
||||
side = side,
|
||||
setting = setting,
|
||||
command = command,
|
||||
}
|
@@ -0,0 +1,46 @@
|
||||
--- The @{craftos.expect} library provides helper functions for verifying that
|
||||
-- function arguments are well-formed and of the correct type.
|
||||
--
|
||||
-- @module craftos.expect
|
||||
|
||||
local native_select, native_type = select, type
|
||||
|
||||
--- Expect an argument to have a specific type.
|
||||
--
|
||||
-- @tparam int index The 1-based argument index.
|
||||
-- @param value The argument's value.
|
||||
-- @tparam string ... The allowed types of the argument.
|
||||
-- @throws If the value is not one of the allowed types.
|
||||
local function expect(index, value, ...)
|
||||
local t = native_type(value)
|
||||
for i = 1, native_select("#", ...) do
|
||||
if t == native_select(i, ...) then return true end
|
||||
end
|
||||
|
||||
local types = table.pack(...)
|
||||
for i = types.n, 1, -1 do
|
||||
if types[i] == "nil" then table.remove(types, i) end
|
||||
end
|
||||
|
||||
local type_names
|
||||
if #types <= 1 then
|
||||
type_names = tostring(...)
|
||||
else
|
||||
type_names = table.concat(types, ", ", 1, #types - 1) .. " or " .. types[#types]
|
||||
end
|
||||
|
||||
-- If we can determine the function name with a high level of confidence, try to include it.
|
||||
local name
|
||||
if native_type(debug) == "table" and native_type(debug.getinfo) == "function" then
|
||||
local ok, info = pcall(debug.getinfo, 3, "nS")
|
||||
if ok and info.name and #info.name ~= "" and info.what ~= "C" then name = info.name end
|
||||
end
|
||||
|
||||
if name then
|
||||
error( ("bad argument #%d to '%s' (expected %s, got %s)"):format(index, name, type_names, t), 3 )
|
||||
else
|
||||
error( ("bad argument #%d (expected %s, got %s)"):format(index, type_names, t), 3 )
|
||||
end
|
||||
end
|
||||
|
||||
return { expect = expect }
|
@@ -0,0 +1,151 @@
|
||||
--- A collection of helper methods for working with shell completion.
|
||||
--
|
||||
-- Most programs may be completed using the @{build} helper method, rather than
|
||||
-- manually switching on the argument index.
|
||||
--
|
||||
-- Note, the helper functions within this module do not accept an argument index,
|
||||
-- and so are not directly usable with the @{shell.setCompletionFunction}. Instead,
|
||||
-- wrap them using @{build}, or your own custom function.
|
||||
--
|
||||
-- @module craftos.shell.completion
|
||||
-- @see cc.completion For more general helpers, suitable for use with @{read}.
|
||||
-- @see shell.setCompletionFunction
|
||||
|
||||
local expect = require "cc.expect".expect
|
||||
local completion = require "cc.completion"
|
||||
|
||||
--- Complete the name of a file relative to the current working directory.
|
||||
--
|
||||
-- @tparam shell shell The shell we're completing in
|
||||
-- @tparam { string... } choices The list of choices to complete from.
|
||||
-- @treturn { string... } A list of suffixes of matching files.
|
||||
local function file(shell, text)
|
||||
return fs.complete(text, shell.dir(), true, false)
|
||||
end
|
||||
|
||||
--- Complete the name of a directory relative to the current working directory.
|
||||
--
|
||||
-- @tparam shell shell The shell we're completing in
|
||||
-- @tparam { string... } choices The list of choices to complete from.
|
||||
-- @treturn { string... } A list of suffixes of matching directories.
|
||||
local function dir(shell, text)
|
||||
return fs.complete(text, shell.dir(), false, true)
|
||||
end
|
||||
|
||||
--- Complete the name of a file or directory relative to the current working
|
||||
-- directory.
|
||||
--
|
||||
-- @tparam shell shell The shell we're completing in
|
||||
-- @tparam { string... } choices The list of choices to complete from.
|
||||
-- @tparam { string... } previous The shell arguments before this one.
|
||||
-- @tparam[opt] boolean add_space Whether to add a space after the completed item.
|
||||
-- @treturn { string... } A list of suffixes of matching files and directories.
|
||||
local function dirOrFile(shell, text, previous, add_space)
|
||||
local results = fs.complete(text, shell.dir(), true, true)
|
||||
if add_space then
|
||||
for n = 1, #results do
|
||||
local result = results[n]
|
||||
if result:sub(-1) ~= "/" then
|
||||
results[n] = result .. " "
|
||||
end
|
||||
end
|
||||
end
|
||||
return results
|
||||
end
|
||||
|
||||
local function wrap(func)
|
||||
return function(shell, text, previous, ...)
|
||||
return func(text, ...)
|
||||
end
|
||||
end
|
||||
|
||||
--- Complete the name of a program.
|
||||
--
|
||||
-- @tparam shell shell The shell we're completing in
|
||||
-- @tparam { string... } choices The list of choices to complete from.
|
||||
-- @treturn { string... } A list of suffixes of matching programs.
|
||||
local function program(shell, text)
|
||||
return shell.completeProgram(text)
|
||||
end
|
||||
|
||||
--- A helper function for building shell completion arguments.
|
||||
--
|
||||
-- This accepts a series of single-argument completion functions, and combines
|
||||
-- them into a function suitable for use with @{shell.setCompletionFunction}.
|
||||
--
|
||||
-- @tparam nil|table|function ... Every argument to @{build} represents an argument
|
||||
-- to the program you wish to complete. Each argument can be one of three types:
|
||||
--
|
||||
-- - `nil`: This argument will not be completed.
|
||||
--
|
||||
-- - A function: This argument will be completed with the given function. It is
|
||||
-- called with the @{shell} object, the string to complete and the arguments
|
||||
-- before this one.
|
||||
--
|
||||
-- - A table: This acts as a more powerful version of the function case. The table
|
||||
-- must have a function as the first item - this will be called with the shell,
|
||||
-- string and preceding arguments as above, but also followed by any additional
|
||||
-- items in the table. This provides a more convenient interface to pass
|
||||
-- options to your completion functions.
|
||||
--
|
||||
-- If this table is the last argument, it may also set the `many` key to true,
|
||||
-- which states this function should be used to complete any remaining arguments.
|
||||
--
|
||||
-- @usage Prompt for a choice of options, followed by a directory, and then multiple
|
||||
-- files.
|
||||
--
|
||||
-- complete.build(
|
||||
-- { complete.choice, { "get", "put" } },
|
||||
-- complete.dir,
|
||||
-- } complete.file, many = true }
|
||||
-- )
|
||||
local function build(...)
|
||||
local arguments = table.pack(...)
|
||||
for i = 1, arguments.n do
|
||||
local arg = arguments[i]
|
||||
if arg ~= nil then
|
||||
expect(i, arg, "table", "function")
|
||||
if type(arg) == "function" then
|
||||
arg = { arg }
|
||||
arguments[i] = arg
|
||||
end
|
||||
|
||||
if type(arg[1]) ~= "function" then
|
||||
error(("Bad table entry #1 at argument #%d (expected function, got %s)"):format(i, type(arg[1])), 2)
|
||||
end
|
||||
|
||||
if arg.many and i < arguments.n then
|
||||
error(("Unexpected 'many' field on argument #%d (should only occur on the last argument)"):format(i), 2)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
return function(shell, index, text, previous)
|
||||
local arg = arguments[index]
|
||||
if not arg then
|
||||
if index <= arguments.n then return end
|
||||
|
||||
arg = arguments[arguments.n]
|
||||
if not arg or not arg.many then return end
|
||||
end
|
||||
|
||||
return arg[1](shell, text, previous, table.unpack(arg, 2))
|
||||
end
|
||||
end
|
||||
|
||||
return {
|
||||
file = file,
|
||||
dir = dir,
|
||||
dirOrFile = dirOrFile,
|
||||
program = program,
|
||||
|
||||
-- Re-export various other functions
|
||||
help = wrap(help.completeTopic),
|
||||
choice = wrap(completion.choice),
|
||||
peripheral = wrap(completion.peripheral),
|
||||
side = wrap(completion.side),
|
||||
setting = wrap(completion.setting),
|
||||
command = wrap(completion.command),
|
||||
|
||||
build = build,
|
||||
}
|
@@ -1,4 +1,4 @@
|
||||
local expect = _G["~expect"]
|
||||
local expect = dofile("rom/modules/main/cc/expect.lua").expect
|
||||
|
||||
-- Setup process switching
|
||||
local parentTerm = term.current()
|
||||
|
@@ -329,7 +329,7 @@ local tMenuFuncs = {
|
||||
printer.setPageTitle( sName.." (page "..nPage..")" )
|
||||
end
|
||||
|
||||
while not printer.newPage() do
|
||||
while not printer.newPage() do
|
||||
if printer.getInkLevel() < 1 then
|
||||
sStatus = "Printer out of ink, please refill"
|
||||
elseif printer.getPaperLevel() < 1 then
|
||||
@@ -342,7 +342,6 @@ local tMenuFuncs = {
|
||||
redrawMenu()
|
||||
term.redirect( printerTerminal )
|
||||
|
||||
local timer = os.startTimer(0.5)
|
||||
sleep(0.5)
|
||||
end
|
||||
|
||||
|
@@ -7,7 +7,7 @@ else
|
||||
end
|
||||
|
||||
if sTopic == "index" then
|
||||
print( "Help topics availiable:" )
|
||||
print( "Help topics available:" )
|
||||
local tTopics = help.topics()
|
||||
textutils.pagedTabulate( tTopics )
|
||||
return
|
||||
|
@@ -1,4 +1,4 @@
|
||||
local expect = _G["~expect"]
|
||||
local expect = dofile("rom/modules/main/cc/expect.lua").expect
|
||||
|
||||
local multishell = multishell
|
||||
local parentShell = shell
|
||||
@@ -56,7 +56,7 @@ local function createShellEnv( sDir )
|
||||
sPath = fs.combine(sDir, sPath)
|
||||
end
|
||||
if fs.exists(sPath) and not fs.isDir(sPath) then
|
||||
local fnFile, sError = loadfile( sPath, tEnv )
|
||||
local fnFile, sError = loadfile( sPath, nil, tEnv )
|
||||
if fnFile then
|
||||
return fnFile, sPath
|
||||
else
|
||||
|
@@ -1,3 +1,4 @@
|
||||
local completion = require "cc.shell.completion"
|
||||
|
||||
-- Setup paths
|
||||
local sPath = ".:/rom/programs"
|
||||
@@ -39,217 +40,86 @@ if term.isColor() then
|
||||
end
|
||||
|
||||
-- Setup completion functions
|
||||
local function completeMultipleChoice( sText, tOptions, bAddSpaces )
|
||||
local tResults = {}
|
||||
for n=1,#tOptions do
|
||||
local sOption = tOptions[n]
|
||||
if #sOption + (bAddSpaces and 1 or 0) > #sText and string.sub( sOption, 1, #sText ) == sText then
|
||||
local sResult = string.sub( sOption, #sText + 1 )
|
||||
if bAddSpaces then
|
||||
table.insert( tResults, sResult .. " " )
|
||||
else
|
||||
table.insert( tResults, sResult )
|
||||
end
|
||||
end
|
||||
end
|
||||
return tResults
|
||||
end
|
||||
local function completePeripheralName( sText, bAddSpaces )
|
||||
return completeMultipleChoice( sText, peripheral.getNames(), bAddSpaces )
|
||||
end
|
||||
local tRedstoneSides = redstone.getSides()
|
||||
local function completeSide( sText, bAddSpaces )
|
||||
return completeMultipleChoice( sText, tRedstoneSides, bAddSpaces )
|
||||
end
|
||||
local function completeFile( shell, nIndex, sText, tPreviousText )
|
||||
if nIndex == 1 then
|
||||
return fs.complete( sText, shell.dir(), true, false )
|
||||
|
||||
local function completePastebinPut(shell, text, previous)
|
||||
if previous[2] == "put" then
|
||||
return fs.complete(text, shell.dir(), true, false )
|
||||
end
|
||||
end
|
||||
local function completeFileMany( shell, nIndex, sText, tPreviousText )
|
||||
return fs.complete( sText, shell.dir(), true, false )
|
||||
end
|
||||
local function completeDir( shell, nIndex, sText, tPreviousText )
|
||||
if nIndex == 1 then
|
||||
return fs.complete( sText, shell.dir(), false, true )
|
||||
end
|
||||
end
|
||||
local function completeEither( shell, nIndex, sText, tPreviousText )
|
||||
if nIndex == 1 then
|
||||
return fs.complete( sText, shell.dir(), true, true )
|
||||
end
|
||||
end
|
||||
local function completeEitherMany( shell, nIndex, sText, tPreviousText )
|
||||
return fs.complete( sText, shell.dir(), true, true )
|
||||
end
|
||||
local function completeEitherEither( shell, nIndex, sText, tPreviousText )
|
||||
if nIndex == 1 then
|
||||
local tResults = fs.complete( sText, shell.dir(), true, true )
|
||||
for n=1,#tResults do
|
||||
local sResult = tResults[n]
|
||||
if string.sub( sResult, #sResult, #sResult ) ~= "/" then
|
||||
tResults[n] = sResult .. " "
|
||||
end
|
||||
end
|
||||
return tResults
|
||||
elseif nIndex == 2 then
|
||||
return fs.complete( sText, shell.dir(), true, true )
|
||||
end
|
||||
end
|
||||
local function completeProgram( shell, nIndex, sText, tPreviousText )
|
||||
if nIndex == 1 then
|
||||
return shell.completeProgram( sText )
|
||||
end
|
||||
end
|
||||
local function completeHelp( shell, nIndex, sText, tPreviousText )
|
||||
if nIndex == 1 then
|
||||
return help.completeTopic( sText )
|
||||
end
|
||||
end
|
||||
local function completeAlias( shell, nIndex, sText, tPreviousText )
|
||||
if nIndex == 2 then
|
||||
return shell.completeProgram( sText )
|
||||
end
|
||||
end
|
||||
local function completePeripheral( shell, nIndex, sText, tPreviousText )
|
||||
if nIndex == 1 then
|
||||
return completePeripheralName( sText )
|
||||
end
|
||||
end
|
||||
local tGPSOptions = { "host", "host ", "locate" }
|
||||
local function completeGPS( shell, nIndex, sText, tPreviousText )
|
||||
if nIndex == 1 then
|
||||
return completeMultipleChoice( sText, tGPSOptions )
|
||||
end
|
||||
end
|
||||
local tLabelOptions = { "get", "get ", "set ", "clear", "clear " }
|
||||
local function completeLabel( shell, nIndex, sText, tPreviousText )
|
||||
if nIndex == 1 then
|
||||
return completeMultipleChoice( sText, tLabelOptions )
|
||||
elseif nIndex == 2 then
|
||||
return completePeripheralName( sText )
|
||||
end
|
||||
end
|
||||
local function completeMonitor( shell, nIndex, sText, tPreviousText )
|
||||
if nIndex == 1 then
|
||||
return completePeripheralName( sText, true )
|
||||
elseif nIndex == 2 then
|
||||
return shell.completeProgram( sText )
|
||||
end
|
||||
end
|
||||
local tRedstoneOptions = { "probe", "set ", "pulse " }
|
||||
local function completeRedstone( shell, nIndex, sText, tPreviousText )
|
||||
if nIndex == 1 then
|
||||
return completeMultipleChoice( sText, tRedstoneOptions )
|
||||
elseif nIndex == 2 then
|
||||
return completeSide( sText )
|
||||
end
|
||||
end
|
||||
local tDJOptions = { "play", "play ", "stop " }
|
||||
local function completeDJ( shell, nIndex, sText, tPreviousText )
|
||||
if nIndex == 1 then
|
||||
return completeMultipleChoice( sText, tDJOptions )
|
||||
elseif nIndex == 2 then
|
||||
return completePeripheralName( sText )
|
||||
end
|
||||
end
|
||||
local tPastebinOptions = { "put ", "get ", "run " }
|
||||
local function completePastebin( shell, nIndex, sText, tPreviousText )
|
||||
if nIndex == 1 then
|
||||
return completeMultipleChoice( sText, tPastebinOptions )
|
||||
elseif nIndex == 2 then
|
||||
if tPreviousText[2] == "put" then
|
||||
return fs.complete( sText, shell.dir(), true, false )
|
||||
end
|
||||
end
|
||||
end
|
||||
local tChatOptions = { "host ", "join " }
|
||||
local function completeChat( shell, nIndex, sText, tPreviousText )
|
||||
if nIndex == 1 then
|
||||
return completeMultipleChoice( sText, tChatOptions )
|
||||
end
|
||||
end
|
||||
local function completeSet( shell, nIndex, sText, tPreviousText )
|
||||
if nIndex == 1 then
|
||||
return completeMultipleChoice( sText, settings.getNames(), true )
|
||||
end
|
||||
end
|
||||
local tCommands
|
||||
if commands then
|
||||
tCommands = commands.list()
|
||||
end
|
||||
local function completeExec( shell, nIndex, sText, tPreviousText )
|
||||
if nIndex == 1 and commands then
|
||||
return completeMultipleChoice( sText, tCommands, true )
|
||||
end
|
||||
end
|
||||
local tWgetOptions = { "run" }
|
||||
local function completeWget( shell, nIndex, sText, tPreviousText )
|
||||
if nIndex == 1 then
|
||||
return completeMultipleChoice( sText, tWgetOptions, true )
|
||||
end
|
||||
end
|
||||
shell.setCompletionFunction( "rom/programs/alias.lua", completeAlias )
|
||||
shell.setCompletionFunction( "rom/programs/cd.lua", completeDir )
|
||||
shell.setCompletionFunction( "rom/programs/copy.lua", completeEitherEither )
|
||||
shell.setCompletionFunction( "rom/programs/delete.lua", completeEitherMany )
|
||||
shell.setCompletionFunction( "rom/programs/drive.lua", completeDir )
|
||||
shell.setCompletionFunction( "rom/programs/edit.lua", completeFile )
|
||||
shell.setCompletionFunction( "rom/programs/eject.lua", completePeripheral )
|
||||
shell.setCompletionFunction( "rom/programs/gps.lua", completeGPS )
|
||||
shell.setCompletionFunction( "rom/programs/help.lua", completeHelp )
|
||||
shell.setCompletionFunction( "rom/programs/id.lua", completePeripheral )
|
||||
shell.setCompletionFunction( "rom/programs/label.lua", completeLabel )
|
||||
shell.setCompletionFunction( "rom/programs/list.lua", completeDir )
|
||||
shell.setCompletionFunction( "rom/programs/mkdir.lua", completeFileMany )
|
||||
shell.setCompletionFunction( "rom/programs/monitor.lua", completeMonitor )
|
||||
shell.setCompletionFunction( "rom/programs/move.lua", completeEitherEither )
|
||||
shell.setCompletionFunction( "rom/programs/redstone.lua", completeRedstone )
|
||||
shell.setCompletionFunction( "rom/programs/rename.lua", completeEitherEither )
|
||||
shell.setCompletionFunction( "rom/programs/shell.lua", completeProgram )
|
||||
shell.setCompletionFunction( "rom/programs/type.lua", completeEither )
|
||||
shell.setCompletionFunction( "rom/programs/set.lua", completeSet )
|
||||
shell.setCompletionFunction( "rom/programs/advanced/bg.lua", completeProgram )
|
||||
shell.setCompletionFunction( "rom/programs/advanced/fg.lua", completeProgram )
|
||||
shell.setCompletionFunction( "rom/programs/fun/dj.lua", completeDJ )
|
||||
shell.setCompletionFunction( "rom/programs/fun/advanced/paint.lua", completeFile )
|
||||
shell.setCompletionFunction( "rom/programs/http/pastebin.lua", completePastebin )
|
||||
shell.setCompletionFunction( "rom/programs/rednet/chat.lua", completeChat )
|
||||
shell.setCompletionFunction( "rom/programs/command/exec.lua", completeExec )
|
||||
shell.setCompletionFunction( "rom/programs/http/wget.lua", completeWget )
|
||||
|
||||
shell.setCompletionFunction( "rom/programs/alias.lua", completion.build(nil, completion.program) )
|
||||
shell.setCompletionFunction( "rom/programs/cd.lua", completion.build(completion.dir) )
|
||||
shell.setCompletionFunction( "rom/programs/copy.lua", completion.build(
|
||||
{ completion.dirOrFile, true },
|
||||
completion.dirOrFile
|
||||
) )
|
||||
shell.setCompletionFunction( "rom/programs/delete.lua", completion.build({ completion.dirOrFile, many = true }) )
|
||||
shell.setCompletionFunction( "rom/programs/drive.lua", completion.build(completion.dir) )
|
||||
shell.setCompletionFunction( "rom/programs/edit.lua", completion.build(completion.file) )
|
||||
shell.setCompletionFunction( "rom/programs/eject.lua", completion.build(completion.peripheral) )
|
||||
shell.setCompletionFunction( "rom/programs/gps.lua", completion.build({ completion.choice, { "host", "host ", "locate" } }) )
|
||||
shell.setCompletionFunction( "rom/programs/help.lua", completion.build(completion.help) )
|
||||
shell.setCompletionFunction( "rom/programs/id.lua", completion.build(completion.peripheral) )
|
||||
shell.setCompletionFunction( "rom/programs/label.lua", completion.build(
|
||||
{ completion.choice, { "get", "get ", "set ", "clear", "clear " } },
|
||||
completion.peripheral
|
||||
) )
|
||||
shell.setCompletionFunction( "rom/programs/list.lua", completion.build(completion.dir) )
|
||||
shell.setCompletionFunction( "rom/programs/mkdir.lua", completion.build({ completion.dir, many = true }) )
|
||||
shell.setCompletionFunction( "rom/programs/monitor.lua", completion.build(
|
||||
{ completion.peripheral, true },
|
||||
completion.program
|
||||
) )
|
||||
shell.setCompletionFunction( "rom/programs/move.lua", completion.build(
|
||||
{ completion.dirOrFile, true },
|
||||
completion.dirOrFile
|
||||
) )
|
||||
shell.setCompletionFunction( "rom/programs/redstone.lua", completion.build(
|
||||
{ completion.choice, { "probe", "set ", "pulse " } },
|
||||
completion.side
|
||||
) )
|
||||
shell.setCompletionFunction( "rom/programs/rename.lua", completion.build(
|
||||
{ completion.dirOrFile, true },
|
||||
completion.dirOrFile
|
||||
) )
|
||||
shell.setCompletionFunction( "rom/programs/shell.lua", completion.build(completion.program) )
|
||||
shell.setCompletionFunction( "rom/programs/type.lua", completion.build(completion.dirOrFile) )
|
||||
shell.setCompletionFunction( "rom/programs/set.lua", completion.build({ completion.setting, true }) )
|
||||
shell.setCompletionFunction( "rom/programs/advanced/bg.lua", completion.build(completion.program) )
|
||||
shell.setCompletionFunction( "rom/programs/advanced/fg.lua", completion.build(completion.program) )
|
||||
shell.setCompletionFunction( "rom/programs/fun/dj.lua", completion.build(
|
||||
{ completion.choice, { "play", "play ", "stop " } },
|
||||
completion.peripheral
|
||||
) )
|
||||
shell.setCompletionFunction( "rom/programs/fun/advanced/paint.lua", completion.build(completion.file) )
|
||||
shell.setCompletionFunction( "rom/programs/http/pastebin.lua", completion.build(
|
||||
{ completion.choice, { "put ", "get ", "run " } },
|
||||
completePastebinPut
|
||||
) )
|
||||
shell.setCompletionFunction( "rom/programs/rednet/chat.lua", completion.build({ completion.choice, { "host ", "join " } }) )
|
||||
shell.setCompletionFunction( "rom/programs/command/exec.lua", completion.build(completion.command) )
|
||||
shell.setCompletionFunction( "rom/programs/http/wget.lua", completion.build({ completion.choice, { "run " } }) )
|
||||
|
||||
if turtle then
|
||||
local tGoOptions = { "left", "right", "forward", "back", "down", "up" }
|
||||
local function completeGo( shell, nIndex, sText )
|
||||
return completeMultipleChoice( sText, tGoOptions, true)
|
||||
end
|
||||
local tTurnOptions = { "left", "right" }
|
||||
local function completeTurn( shell, nIndex, sText )
|
||||
return completeMultipleChoice( sText, tTurnOptions, true )
|
||||
end
|
||||
local tEquipOptions = { "left", "right" }
|
||||
local function completeEquip( shell, nIndex, sText )
|
||||
if nIndex == 2 then
|
||||
return completeMultipleChoice( sText, tEquipOptions )
|
||||
end
|
||||
end
|
||||
local function completeUnequip( shell, nIndex, sText )
|
||||
if nIndex == 1 then
|
||||
return completeMultipleChoice( sText, tEquipOptions )
|
||||
end
|
||||
end
|
||||
shell.setCompletionFunction( "rom/programs/turtle/go.lua", completeGo )
|
||||
shell.setCompletionFunction( "rom/programs/turtle/turn.lua", completeTurn )
|
||||
shell.setCompletionFunction( "rom/programs/turtle/equip.lua", completeEquip )
|
||||
shell.setCompletionFunction( "rom/programs/turtle/unequip.lua", completeUnequip )
|
||||
shell.setCompletionFunction( "rom/programs/turtle/go.lua", completion.build(
|
||||
{ completion.choice, { "left", "right", "forward", "back", "down", "up" }, true, many = true }
|
||||
) )
|
||||
shell.setCompletionFunction( "rom/programs/turtle/turn.lua", completion.build(
|
||||
{ completion.choice, { "left", "right" }, true, many = true }
|
||||
))
|
||||
shell.setCompletionFunction( "rom/programs/turtle/equip.lua", completion.build(
|
||||
nil,
|
||||
{ completion.choice, { "left", "right" } }
|
||||
) )
|
||||
shell.setCompletionFunction( "rom/programs/turtle/unequip.lua", completion.build(
|
||||
{ completion.choice, { "left", "right" } }
|
||||
) )
|
||||
end
|
||||
|
||||
|
||||
-- Run autorun files
|
||||
if fs.exists( "/rom/autorun" ) and fs.isDir( "/rom/autorun" ) then
|
||||
local tFiles = fs.list( "/rom/autorun" )
|
||||
table.sort( tFiles )
|
||||
for n, sFile in ipairs( tFiles ) do
|
||||
for _, sFile in ipairs( tFiles ) do
|
||||
if string.sub( sFile, 1, 1 ) ~= "." then
|
||||
local sPath = "/rom/autorun/"..sFile
|
||||
if not fs.isDir( sPath ) then
|
||||
|
@@ -90,7 +90,7 @@ public class ComputerTestDelegate
|
||||
try( WritableByteChannel channel = mount.openChannelForWrite( "startup.lua" );
|
||||
Writer writer = Channels.newWriter( channel, StandardCharsets.UTF_8.newEncoder(), -1 ) )
|
||||
{
|
||||
writer.write( "loadfile('test/mcfly.lua', _ENV)('test/spec') cct_test.finish()" );
|
||||
writer.write( "loadfile('test/mcfly.lua', nil, _ENV)('test/spec') cct_test.finish()" );
|
||||
}
|
||||
|
||||
computer = new Computer( new BasicEnvironment( mount ), term, 0 );
|
||||
|
@@ -33,7 +33,7 @@ public class ComputerBootstrap
|
||||
{
|
||||
MemoryMount mount = new MemoryMount()
|
||||
.addFile( "test.lua", program )
|
||||
.addFile( "startup", "assertion.assert(pcall(loadfile('test.lua', _ENV))) os.shutdown()" );
|
||||
.addFile( "startup", "assertion.assert(pcall(loadfile('test.lua', nil, _ENV))) os.shutdown()" );
|
||||
|
||||
run( mount, x -> { } );
|
||||
}
|
||||
|
@@ -27,18 +27,58 @@ local function check(func, arg, ty, val)
|
||||
end
|
||||
end
|
||||
|
||||
--- A stub - wraps a value within a a table,
|
||||
local stub_mt = {}
|
||||
stub_mt.__index = stub_mt
|
||||
|
||||
--- Revert this stub, restoring the previous value.
|
||||
--
|
||||
-- Note, a stub can only be reverted once.
|
||||
function stub_mt:revert()
|
||||
if not self.active then return end
|
||||
|
||||
self.active = false
|
||||
rawset(self.stubbed_in, self.key, self.original)
|
||||
end
|
||||
|
||||
local active_stubs = {}
|
||||
|
||||
--- Stub a global variable with a specific value
|
||||
--
|
||||
-- @tparam string var The variable to stub
|
||||
-- @param value The value to stub it with
|
||||
local function stub(tbl, var, value)
|
||||
check('stub', 1, 'table', tbl)
|
||||
check('stub', 2, 'string', var)
|
||||
local function default_stub() end
|
||||
|
||||
table.insert(active_stubs, { tbl = tbl, var = var, value = tbl[var] })
|
||||
rawset(tbl, var, value)
|
||||
--- Stub a table entry with a new value.
|
||||
--
|
||||
-- @tparam table
|
||||
-- @tparam string key The variable to stub
|
||||
-- @param[opt] value The value to stub it with. If this is a function, one can
|
||||
-- use the various stub expectation methods to determine what it was called
|
||||
-- with. Defaults to an empty function - pass @{nil} in explicitly to set the
|
||||
-- value to nil.
|
||||
-- @treturn Stub The resulting stub
|
||||
local function stub(tbl, key, ...)
|
||||
check('stub', 1, 'table', tbl)
|
||||
check('stub', 2, 'string', key)
|
||||
|
||||
local stub = setmetatable({
|
||||
active = true,
|
||||
stubbed_in = tbl,
|
||||
key = key,
|
||||
original = rawget(tbl, key),
|
||||
}, stub_mt)
|
||||
|
||||
local value = ...
|
||||
if select('#', ...) == 0 then value = default_stub end
|
||||
if type(value) == "function" then
|
||||
local arguments, delegate = {}, value
|
||||
stub.arguments = arguments
|
||||
value = function(...)
|
||||
arguments[#arguments + 1] = table.pack(...)
|
||||
return delegate(...)
|
||||
end
|
||||
end
|
||||
|
||||
table.insert(active_stubs, stub)
|
||||
rawset(tbl, key, value)
|
||||
return stub
|
||||
end
|
||||
|
||||
--- Capture the current global state of the computer
|
||||
@@ -51,16 +91,14 @@ local function push_state()
|
||||
output = io.output(),
|
||||
dir = shell.dir(),
|
||||
path = shell.path(),
|
||||
aliases = shell.aliases(),
|
||||
stubs = stubs,
|
||||
}
|
||||
end
|
||||
|
||||
--- Restore the global state of the computer to a previous version
|
||||
local function pop_state(state)
|
||||
for i = #active_stubs, 1, -1 do
|
||||
local stub = active_stubs[i]
|
||||
rawset(stub.tbl, stub.var, stub.value)
|
||||
end
|
||||
for i = #active_stubs, 1, -1 do active_stubs[i]:revert() end
|
||||
|
||||
active_stubs = state.stubs
|
||||
|
||||
@@ -69,6 +107,14 @@ local function pop_state(state)
|
||||
io.output(state.output)
|
||||
shell.setDir(state.dir)
|
||||
shell.setPath(state.path)
|
||||
|
||||
local aliases = shell.aliases()
|
||||
for k in pairs(aliases) do
|
||||
if not state.aliases[k] then shell.clearAlias(k) end
|
||||
end
|
||||
for k, v in pairs(state.aliases) do
|
||||
if aliases[k] ~= v then shell.setAlias(k, v) end
|
||||
end
|
||||
end
|
||||
|
||||
local error_mt = { __tostring = function(self) return self.message end }
|
||||
@@ -210,6 +256,16 @@ local function matches(eq, exact, left, right)
|
||||
return true
|
||||
end
|
||||
|
||||
local function pairwise_equal(left, right)
|
||||
if left.n ~= right.n then return false end
|
||||
|
||||
for i = 1, left.n do
|
||||
if left[i] ~= right[i] then return false end
|
||||
end
|
||||
|
||||
return true
|
||||
end
|
||||
|
||||
--- Assert that this expectation is structurally equivalent to
|
||||
-- the provided object.
|
||||
--
|
||||
@@ -236,6 +292,70 @@ function expect_mt:matches(value)
|
||||
return self
|
||||
end
|
||||
|
||||
--- Assert that this stub was called a specific number of times.
|
||||
--
|
||||
-- @tparam[opt] number The exact number of times the function must be called.
|
||||
-- If not given just require the function to be called at least once.
|
||||
-- @raises If this function was not called the expected number of times.
|
||||
function expect_mt:called(times)
|
||||
if getmetatable(self.value) ~= stub_mt or self.value.arguments == nil then
|
||||
fail(("Expected stubbed function, got %s"):format(type(self.value)))
|
||||
end
|
||||
|
||||
local called = #self.value.arguments
|
||||
|
||||
if times == nil then
|
||||
if called == 0 then
|
||||
fail("Expected stub to be called\nbut it was not.")
|
||||
end
|
||||
else
|
||||
check('stub', 1, 'number', times)
|
||||
if called ~= times then
|
||||
fail(("Expected stub to be called %d times\nbut was called %d times."):format(times, called))
|
||||
end
|
||||
end
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
local function called_with_check(eq, self, ...)
|
||||
if getmetatable(self.value) ~= stub_mt or self.value.arguments == nil then
|
||||
fail(("Expected stubbed function, got %s"):format(type(self.value)))
|
||||
end
|
||||
|
||||
local exp_args = table.pack(...)
|
||||
local actual_args = self.value.arguments
|
||||
for i = 1, #actual_args do
|
||||
if eq(actual_args[i], exp_args) then return self end
|
||||
end
|
||||
|
||||
local head = ("Expected stub to be called with %s\nbut was"):format(format(exp_args))
|
||||
if #actual_args == 0 then
|
||||
fail(head .. " not called at all")
|
||||
elseif #actual_args == 1 then
|
||||
fail(("%s called with %s."):format(head, format(actual_args[1])))
|
||||
else
|
||||
local lines = { head .. " called with:" }
|
||||
for i = 1, #actual_args do lines[i + 1] = " - " .. format(actual_args[i]) end
|
||||
|
||||
fail(table.concat(lines, "\n"))
|
||||
end
|
||||
end
|
||||
|
||||
--- Assert that this stub was called with a set of arguments
|
||||
--
|
||||
-- Arguments are compared using exact equality.
|
||||
function expect_mt:called_with(...)
|
||||
return called_with_check(pairwise_equal, self, ...)
|
||||
end
|
||||
|
||||
--- Assert that this stub was called with a set of arguments
|
||||
--
|
||||
-- Arguments are compared using matching.
|
||||
function expect_mt:called_with_matching(...)
|
||||
return called_with_check(matches, self, ...)
|
||||
end
|
||||
|
||||
local expect = setmetatable( {
|
||||
--- Construct an expectation on the error message calling this function
|
||||
-- produces
|
||||
@@ -381,7 +501,7 @@ do
|
||||
if fs.isDir(file) then
|
||||
run_in(file)
|
||||
elseif file:sub(-#suffix) == suffix then
|
||||
local fun, err = loadfile(file, env)
|
||||
local fun, err = loadfile(file, nil, env)
|
||||
if not fun then
|
||||
do_test { name = file:sub(#root_dir + 2), error = { message = err } }
|
||||
else
|
||||
|
@@ -118,6 +118,43 @@ describe("The window library", function()
|
||||
expect.error(w.reposition, 1, 1, false, 1):eq("bad argument #3 (expected number, got boolean)")
|
||||
expect.error(w.reposition, 1, 1, nil, 1):eq("bad argument #3 (expected number, got nil)")
|
||||
expect.error(w.reposition, 1, 1, 1, nil):eq("bad argument #4 (expected number, got nil)")
|
||||
expect.error(w.reposition, 1, 1, 1, 1, true):eq("bad argument #5 (expected table, got boolean)")
|
||||
end)
|
||||
|
||||
it("can change the buffer", function()
|
||||
local a, b = mk(), mk()
|
||||
local target = window.create(a, 1, 1, a.getSize())
|
||||
|
||||
target.write("Test")
|
||||
expect((a.getLine(1))):equal("Test ")
|
||||
expect({ a.getCursorPos() }):same { 5, 1 }
|
||||
|
||||
target.reposition(1, 1, nil, nil, b)
|
||||
|
||||
target.redraw()
|
||||
expect((a.getLine(1))):equal("Test ")
|
||||
expect({ a.getCursorPos() }):same { 5, 1 }
|
||||
|
||||
target.setCursorPos(1, 1) target.write("More")
|
||||
expect((a.getLine(1))):equal("Test ")
|
||||
expect((b.getLine(1))):equal("More ")
|
||||
end)
|
||||
end)
|
||||
|
||||
describe("Window.getLine", function()
|
||||
it("validates arguments", function()
|
||||
local w = mk()
|
||||
w.getLine(1)
|
||||
local _, y = w.getSize()
|
||||
expect.error(w.getLine, nil):eq("bad argument #1 (expected number, got nil)")
|
||||
expect.error(w.getLine, 0):eq("Line is out of range.")
|
||||
expect.error(w.getLine, y + 1):eq("Line is out of range.")
|
||||
end)
|
||||
|
||||
it("provides a line's contents", function()
|
||||
local w = mk()
|
||||
w.blit("test", "aaaa", "4444")
|
||||
expect({ w.getLine(1) }):same { "test ", "aaaa0", "4444f" }
|
||||
end)
|
||||
end)
|
||||
end)
|
||||
|
@@ -1,36 +1,4 @@
|
||||
describe("The Lua base library", function()
|
||||
describe("expect", function()
|
||||
local e = _G["~expect"]
|
||||
|
||||
it("checks a single type", function()
|
||||
expect(e(1, "test", "string")):eq(true)
|
||||
expect(e(1, 2, "number")):eq(true)
|
||||
|
||||
expect.error(e, 1, nil, "string"):eq("bad argument #1 (expected string, got nil)")
|
||||
expect.error(e, 2, 1, "nil"):eq("bad argument #2 (expected nil, got number)")
|
||||
end)
|
||||
|
||||
it("checks multiple types", function()
|
||||
expect(e(1, "test", "string", "number")):eq(true)
|
||||
expect(e(1, 2, "string", "number")):eq(true)
|
||||
|
||||
expect.error(e, 1, nil, "string", "number"):eq("bad argument #1 (expected string or number, got nil)")
|
||||
expect.error(e, 2, false, "string", "table", "number", "nil")
|
||||
:eq("bad argument #2 (expected string, table or number, got boolean)")
|
||||
end)
|
||||
|
||||
it("includes the function name", function()
|
||||
local function worker()
|
||||
expect(e(1, nil, "string")):eq(true)
|
||||
end
|
||||
local function trampoline()
|
||||
worker()
|
||||
end
|
||||
|
||||
expect.error(trampoline):eq("base_spec.lua:27: bad argument #1 to 'worker' (expected string, got nil)")
|
||||
end)
|
||||
end)
|
||||
|
||||
describe("sleep", function()
|
||||
it("validates arguments", function()
|
||||
sleep(0)
|
||||
@@ -48,18 +16,43 @@ describe("The Lua base library", function()
|
||||
end)
|
||||
|
||||
describe("loadfile", function()
|
||||
local function make_file()
|
||||
local tmp = fs.open("test-files/out.lua", "w")
|
||||
tmp.write("return _ENV")
|
||||
tmp.close()
|
||||
end
|
||||
|
||||
it("validates arguments", function()
|
||||
loadfile("")
|
||||
loadfile("", {})
|
||||
loadfile("", "")
|
||||
loadfile("", "", {})
|
||||
|
||||
expect.error(loadfile, nil):eq("bad argument #1 (expected string, got nil)")
|
||||
expect.error(loadfile, "", false):eq("bad argument #2 (expected table, got boolean)")
|
||||
expect.error(loadfile, "", false):eq("bad argument #2 (expected string, got boolean)")
|
||||
expect.error(loadfile, "", "", false):eq("bad argument #3 (expected table, got boolean)")
|
||||
end)
|
||||
|
||||
it("prefixes the filename with @", function()
|
||||
local info = debug.getinfo(loadfile("/rom/startup.lua"), "S")
|
||||
expect(info):matches { short_src = "startup.lua", source = "@startup.lua" }
|
||||
end)
|
||||
|
||||
it("loads a file with the global environment", function()
|
||||
make_file()
|
||||
expect(loadfile("test-files/out.lua")()):eq(_G)
|
||||
end)
|
||||
|
||||
it("loads a file with a specific environment", function()
|
||||
make_file()
|
||||
local env = {}
|
||||
expect(loadfile("test-files/out.lua", nil, env)()):eq(env)
|
||||
end)
|
||||
|
||||
it("supports the old-style argument form", function()
|
||||
make_file()
|
||||
local env = {}
|
||||
expect(loadfile("test-files/out.lua", env)()):eq(env)
|
||||
end)
|
||||
end)
|
||||
|
||||
describe("dofile", function()
|
||||
|
@@ -0,0 +1,57 @@
|
||||
describe("cc.completion", function()
|
||||
local c = require("cc.completion")
|
||||
|
||||
describe("choice", function()
|
||||
it("provides all choices", function()
|
||||
expect(c.choice("", { "some text", "some other", "other" }))
|
||||
:same { "some text", "some other", "other" }
|
||||
end)
|
||||
|
||||
it("provides a filtered list of choices", function()
|
||||
expect(c.choice("som", { "some text", "some other", "other" }))
|
||||
:same { "e text", "e other" }
|
||||
|
||||
expect(c.choice("none", { "some text", "some other", "other" }))
|
||||
:same { }
|
||||
end)
|
||||
|
||||
it("adds text if needed", function()
|
||||
expect(c.choice("som", { "some text", "some other", "other" }, true))
|
||||
:same { "e text ", "e other " }
|
||||
end)
|
||||
end)
|
||||
|
||||
describe("peripheral", function()
|
||||
it("provides a choice of peripherals", function()
|
||||
stub(peripheral, "getNames", function() return { "drive_0", "left" } end)
|
||||
|
||||
expect(c.peripheral("dri")):same { "ve_0" }
|
||||
expect(c.peripheral("dri", true)):same { "ve_0 " }
|
||||
end)
|
||||
end)
|
||||
|
||||
describe("side", function()
|
||||
it("provides a choice of sides", function()
|
||||
expect(c.side("le")):same { "ft" }
|
||||
expect(c.side("le", true)):same { "ft " }
|
||||
end)
|
||||
end)
|
||||
|
||||
describe("setting", function()
|
||||
it("provides a choice of setting names", function()
|
||||
stub(settings, "getNames", function() return { "shell.allow_startup", "list.show_hidden" } end)
|
||||
|
||||
expect(c.setting("li")):same { "st.show_hidden" }
|
||||
expect(c.setting("li", true)):same { "st.show_hidden " }
|
||||
end)
|
||||
end)
|
||||
|
||||
describe("command", function()
|
||||
it("provides a choice of command names", function()
|
||||
stub(_G, "commands", { list = function() return { "list", "say" } end })
|
||||
|
||||
expect(c.command("li")):same { "st" }
|
||||
expect(c.command("li", true)):same { "st " }
|
||||
end)
|
||||
end)
|
||||
end)
|
31
src/test/resources/test-rom/spec/modules/cc/expect_spec.lua
Normal file
31
src/test/resources/test-rom/spec/modules/cc/expect_spec.lua
Normal file
@@ -0,0 +1,31 @@
|
||||
describe("cc.expect", function()
|
||||
local e = require("cc.expect")
|
||||
|
||||
it("checks a single type", function()
|
||||
expect(e.expect(1, "test", "string")):eq(true)
|
||||
expect(e.expect(1, 2, "number")):eq(true)
|
||||
|
||||
expect.error(e.expect, 1, nil, "string"):eq("bad argument #1 (expected string, got nil)")
|
||||
expect.error(e.expect, 2, 1, "nil"):eq("bad argument #2 (expected nil, got number)")
|
||||
end)
|
||||
|
||||
it("checks multiple types", function()
|
||||
expect(e.expect(1, "test", "string", "number")):eq(true)
|
||||
expect(e.expect(1, 2, "string", "number")):eq(true)
|
||||
|
||||
expect.error(e.expect, 1, nil, "string", "number"):eq("bad argument #1 (expected string or number, got nil)")
|
||||
expect.error(e.expect, 2, false, "string", "table", "number", "nil")
|
||||
:eq("bad argument #2 (expected string, table or number, got boolean)")
|
||||
end)
|
||||
|
||||
it("includes the function name", function()
|
||||
local function worker()
|
||||
expect(e.expect(1, nil, "string")):eq(true)
|
||||
end
|
||||
local function trampoline()
|
||||
worker()
|
||||
end
|
||||
|
||||
expect.error(trampoline):eq("expect_spec.lua:26: bad argument #1 to 'worker' (expected string, got nil)")
|
||||
end)
|
||||
end)
|
@@ -0,0 +1,41 @@
|
||||
describe("cc.shell.completion", function()
|
||||
local c = require "cc.shell.completion"
|
||||
|
||||
describe("dirOrFile", function()
|
||||
it("completes both", function()
|
||||
expect(c.dirOrFile(shell, "rom/")):same {
|
||||
"apis/", "apis", "autorun/", "autorun", "help/", "help",
|
||||
"modules/", "modules", "motd.txt", "programs/", "programs", "startup.lua"
|
||||
}
|
||||
end)
|
||||
|
||||
it("adds a space", function()
|
||||
expect(c.dirOrFile(shell, "rom/", nil, true)):same {
|
||||
"apis/", "apis ", "autorun/", "autorun ", "help/", "help ",
|
||||
"modules/", "modules ", "motd.txt ", "programs/", "programs ", "startup.lua ",
|
||||
}
|
||||
end)
|
||||
end)
|
||||
|
||||
describe("build", function()
|
||||
it("completes multiple arguments", function()
|
||||
local spec = c.build(
|
||||
function() return { "a", "b", "c" } end,
|
||||
nil,
|
||||
{ c.choice, { "d", "e", "f"} }
|
||||
)
|
||||
|
||||
expect(spec(shell, 1, "")):same { "a", "b", "c" }
|
||||
expect(spec(shell, 2, "")):same(nil)
|
||||
expect(spec(shell, 3, "")):same { "d", "e", "f" }
|
||||
expect(spec(shell, 4, "")):same(nil)
|
||||
end)
|
||||
|
||||
it("supports variadic completions", function()
|
||||
local spec = c.build({ function() return { "a", "b", "c" } end, many = true })
|
||||
|
||||
expect(spec(shell, 1, "")):same({ "a", "b", "c" })
|
||||
expect(spec(shell, 2, "")):same({ "a", "b", "c" })
|
||||
end)
|
||||
end)
|
||||
end)
|
@@ -0,0 +1,11 @@
|
||||
local capture = require "test_helpers".capture_program
|
||||
|
||||
describe("The bg program", function()
|
||||
it("opens a tab in the background", function()
|
||||
local openTab = stub(shell, "openTab", function() return 12 end)
|
||||
local switchTab = stub(shell, "switchTab")
|
||||
capture(stub, "bg")
|
||||
expect(openTab):called_with("shell")
|
||||
expect(switchTab):called(0)
|
||||
end)
|
||||
end)
|
@@ -0,0 +1,11 @@
|
||||
local capture = require "test_helpers".capture_program
|
||||
|
||||
describe("The fg program", function()
|
||||
it("opens the shell in the foreground", function()
|
||||
local openTab = stub(shell, "openTab", function() return 12 end)
|
||||
local switchTab = stub(shell, "switchTab")
|
||||
capture(stub, "fg")
|
||||
expect(openTab):called_with("shell")
|
||||
expect(switchTab):called_with(12)
|
||||
end)
|
||||
end)
|
28
src/test/resources/test-rom/spec/programs/alias_spec.lua
Normal file
28
src/test/resources/test-rom/spec/programs/alias_spec.lua
Normal file
@@ -0,0 +1,28 @@
|
||||
local capture = require "test_helpers".capture_program
|
||||
|
||||
describe("The alias program", function()
|
||||
it("displays its usage when given too many arguments", function()
|
||||
expect(capture(stub, "alias a b c"))
|
||||
:matches { ok = true, output = "Usage: alias <alias> <program>\n", error = "" }
|
||||
end)
|
||||
|
||||
it("lists aliases", function()
|
||||
local pagedTabulate = stub(textutils, "pagedTabulate", function(x) print(table.unpack(x)) end)
|
||||
stub(shell, "aliases", function() return { cp = "copy" } end)
|
||||
expect(capture(stub, "alias"))
|
||||
:matches { ok = true, output = "cp:copy\n", error = "" }
|
||||
expect(pagedTabulate):called_with_matching({ "cp:copy" })
|
||||
end)
|
||||
|
||||
it("sets an alias", function()
|
||||
local setAlias = stub(shell, "setAlias")
|
||||
capture(stub, "alias test Hello")
|
||||
expect(setAlias):called_with("test", "Hello")
|
||||
end)
|
||||
|
||||
it("clears an alias", function()
|
||||
local clearAlias = stub(shell, "clearAlias")
|
||||
capture(stub, "alias test")
|
||||
expect(clearAlias):called_with("test")
|
||||
end)
|
||||
end)
|
@@ -1,19 +1,18 @@
|
||||
local capture = require "test_helpers".capture_program
|
||||
|
||||
describe("The cd program", function()
|
||||
|
||||
it("cd into a directory", function()
|
||||
shell.run("cd /rom/programs")
|
||||
|
||||
expect(shell.dir()):eq("rom/programs")
|
||||
it("changes into a directory", function()
|
||||
local setDir = stub(shell, "setDir")
|
||||
capture(stub, "cd /rom/programs")
|
||||
expect(setDir):called_with("rom/programs")
|
||||
end)
|
||||
|
||||
it("cd into a not existing directory", function()
|
||||
it("does not move into a non-existent directory", function()
|
||||
expect(capture(stub, "cd /rom/nothing"))
|
||||
:matches { ok = true, output = "Not a directory\n", error = "" }
|
||||
end)
|
||||
|
||||
it("displays the usage with no arguments", function()
|
||||
|
||||
it("displays the usage when given no arguments", function()
|
||||
expect(capture(stub, "cd"))
|
||||
:matches { ok = true, output = "Usage: cd <path>\n", error = "" }
|
||||
end)
|
||||
|
13
src/test/resources/test-rom/spec/programs/clear_spec.lua
Normal file
13
src/test/resources/test-rom/spec/programs/clear_spec.lua
Normal file
@@ -0,0 +1,13 @@
|
||||
local capture = require "test_helpers".capture_program
|
||||
|
||||
describe("The clear program", function()
|
||||
it("clears the screen", function()
|
||||
local clear = stub(term, "clear")
|
||||
local setCursorPos = stub(term, "setCursorPos")
|
||||
|
||||
capture(stub, "clear")
|
||||
|
||||
expect(clear):called(1)
|
||||
expect(setCursorPos):called_with(1, 1)
|
||||
end)
|
||||
end)
|
@@ -0,0 +1,20 @@
|
||||
local capture = require "test_helpers".capture_program
|
||||
|
||||
describe("The commands program", function()
|
||||
it("displays an error without the commands api", function()
|
||||
stub(_G, "commands", nil)
|
||||
expect(capture(stub, "/rom/programs/command/commands.lua"))
|
||||
:matches { ok = true, output = "", error = "Requires a Command Computer.\n" }
|
||||
end)
|
||||
|
||||
it("lists commands", function()
|
||||
local pagedTabulate = stub(textutils, "pagedTabulate", function(x) print(table.unpack(x)) end)
|
||||
stub(_G, "commands", {
|
||||
list = function() return { "computercraft" } end
|
||||
})
|
||||
|
||||
expect(capture(stub, "/rom/programs/command/commands.lua"))
|
||||
:matches { ok = true, output = "Available commands:\ncomputercraft\n", error = "" }
|
||||
expect(pagedTabulate):called_with_matching({ "computercraft" })
|
||||
end)
|
||||
end)
|
@@ -0,0 +1,33 @@
|
||||
local capture = require "test_helpers".capture_program
|
||||
|
||||
describe("The exec program", function()
|
||||
it("displays an error without the commands api", function()
|
||||
stub(_G, "commands", nil)
|
||||
expect(capture(stub, "/rom/programs/command/exec.lua"))
|
||||
:matches { ok = true, output = "", error = "Requires a Command Computer.\n" }
|
||||
end)
|
||||
|
||||
it("displays its usage when given no argument", function()
|
||||
stub(_G, "commands", {})
|
||||
expect(capture(stub, "/rom/programs/command/exec.lua"))
|
||||
:matches { ok = true, output = "", error = "Usage: exec <command>\n" }
|
||||
end)
|
||||
|
||||
it("runs a command", function()
|
||||
stub(_G, "commands", {
|
||||
exec = function() return true, {"Hello World!"} end
|
||||
})
|
||||
|
||||
expect(capture(stub, "/rom/programs/command/exec.lua computercraft"))
|
||||
:matches { ok = true, output = "Success\nHello World!\n", error = "" }
|
||||
end)
|
||||
|
||||
it("reports command failures", function()
|
||||
stub(_G,"commands",{
|
||||
exec = function() return false, {"Hello World!"} end
|
||||
})
|
||||
|
||||
expect(capture(stub, "/rom/programs/command/exec.lua computercraft"))
|
||||
:matches { ok = true, output = "Hello World!\n", error = "Failed\n" }
|
||||
end)
|
||||
end)
|
16
src/test/resources/test-rom/spec/programs/drive_spec.lua
Normal file
16
src/test/resources/test-rom/spec/programs/drive_spec.lua
Normal file
@@ -0,0 +1,16 @@
|
||||
local capture = require "test_helpers".capture_program
|
||||
|
||||
describe("The drive program", function()
|
||||
it("run the program", function()
|
||||
local getFreeSpace = stub(fs, "getFreeSpace", function() return 1234e4 end)
|
||||
|
||||
expect(capture(stub, "drive"))
|
||||
:matches { ok = true, output = "hdd (12.3MB remaining)\n", error = "" }
|
||||
expect(getFreeSpace):called(1):called_with("")
|
||||
end)
|
||||
|
||||
it("fails on a non-existent path", function()
|
||||
expect(capture(stub, "drive /rom/nothing"))
|
||||
:matches { ok = true, output = "No such path\n", error = "" }
|
||||
end)
|
||||
end)
|
@@ -1,10 +1,9 @@
|
||||
local capture = require "test_helpers".capture_program
|
||||
local testFile = require "test_helpers".testFile
|
||||
|
||||
describe("The edit program", function()
|
||||
|
||||
it("displays its usage when given no argument", function()
|
||||
multishell = nil
|
||||
|
||||
it("displays its usage when given no argument", function()
|
||||
expect(capture(stub, "edit"))
|
||||
:matches { ok = true, output = "Usage: edit <path>\n", error = "" }
|
||||
end)
|
||||
|
13
src/test/resources/test-rom/spec/programs/eject_spec.lua
Normal file
13
src/test/resources/test-rom/spec/programs/eject_spec.lua
Normal file
@@ -0,0 +1,13 @@
|
||||
local capture = require "test_helpers".capture_program
|
||||
|
||||
describe("The eject program", function()
|
||||
it("displays its usage when given no argument", function()
|
||||
expect(capture(stub, "eject"))
|
||||
:matches { ok = true, output = "Usage: eject <drive>\n", error = "" }
|
||||
end)
|
||||
|
||||
it("fails when trying to eject a non-drive", function()
|
||||
expect(capture(stub, "eject /rom"))
|
||||
:matches { ok = true, output = "Nothing in /rom drive\n", error = "" }
|
||||
end)
|
||||
end)
|
9
src/test/resources/test-rom/spec/programs/exit_spec.lua
Normal file
9
src/test/resources/test-rom/spec/programs/exit_spec.lua
Normal file
@@ -0,0 +1,9 @@
|
||||
local capture = require "test_helpers".capture_program
|
||||
|
||||
describe("The exit program", function()
|
||||
it("exits the shell", function()
|
||||
local exit = stub(shell, "exit")
|
||||
expect(capture(stub, "exit")):matches { ok = true, combined = "" }
|
||||
expect(exit):called(1)
|
||||
end)
|
||||
end)
|
@@ -0,0 +1,8 @@
|
||||
local capture = require "test_helpers".capture_program
|
||||
|
||||
describe("The paint program", function()
|
||||
it("displays its usage when given no arguments", function()
|
||||
expect(capture(stub, "paint"))
|
||||
:matches { ok = true, output = "Usage: paint <path>\n", error = "" }
|
||||
end)
|
||||
end)
|
13
src/test/resources/test-rom/spec/programs/fun/dj_spec.lua
Normal file
13
src/test/resources/test-rom/spec/programs/fun/dj_spec.lua
Normal file
@@ -0,0 +1,13 @@
|
||||
local capture = require "test_helpers".capture_program
|
||||
|
||||
describe("The dj program", function()
|
||||
it("displays its usage when given too many arguments", function()
|
||||
expect(capture(stub, "dj a b c"))
|
||||
:matches { ok = true, output = "Usages:\ndj play\ndj play <drive>\ndj stop\n", error = "" }
|
||||
end)
|
||||
|
||||
it("fails when no disks are present", function()
|
||||
expect(capture(stub, "dj"))
|
||||
:matches { ok = true, output = "No Music Discs in attached disk drives\n", error = "" }
|
||||
end)
|
||||
end)
|
10
src/test/resources/test-rom/spec/programs/fun/hello_spec.lua
Normal file
10
src/test/resources/test-rom/spec/programs/fun/hello_spec.lua
Normal file
@@ -0,0 +1,10 @@
|
||||
local capture = require "test_helpers".capture_program
|
||||
|
||||
describe("The hello program", function()
|
||||
it("says hello", function()
|
||||
local slowPrint = stub(textutils, "slowPrint", function(...) return print(...) end)
|
||||
expect(capture(stub, "hello"))
|
||||
:matches { ok = true, output = "Hello World!\n", error = "" }
|
||||
expect(slowPrint):called(1)
|
||||
end)
|
||||
end)
|
23
src/test/resources/test-rom/spec/programs/gps_spec.lua
Normal file
23
src/test/resources/test-rom/spec/programs/gps_spec.lua
Normal file
@@ -0,0 +1,23 @@
|
||||
local capture = require "test_helpers".capture_program
|
||||
|
||||
describe("The gps program", function()
|
||||
it("displays its usage when given no arguments", function()
|
||||
expect(capture(stub, "gps"))
|
||||
:matches { ok = true, output = "Usages:\ngps host\ngps host <x> <y> <z>\ngps locate\n", error = "" }
|
||||
end)
|
||||
|
||||
it("fails on a pocket computer", function()
|
||||
stub(_G, "pocket", {})
|
||||
|
||||
expect(capture(stub, "gps host"))
|
||||
:matches { ok = true, output = "GPS Hosts must be stationary\n", error = "" }
|
||||
end)
|
||||
|
||||
it("can locate the computer", function()
|
||||
local locate = stub(gps, "locate", function() print("Some debugging information.") end)
|
||||
|
||||
expect(capture(stub, "gps locate"))
|
||||
:matches { ok = true, output = "Some debugging information.\n", error = "" }
|
||||
expect(locate):called_with(2, true)
|
||||
end)
|
||||
end)
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user