Merge branch 'mc-1.15.x' into mc-1.16.x
22
.gitpod.yml
Normal file
@ -0,0 +1,22 @@
|
||||
image:
|
||||
file: config/gitpod/Dockerfile
|
||||
|
||||
ports:
|
||||
- port: 25565
|
||||
onOpen: notify
|
||||
|
||||
vscode:
|
||||
extensions:
|
||||
- eamodio.gitlens
|
||||
- github.vscode-pull-request-github
|
||||
- ms-azuretools.vscode-docker
|
||||
- redhat.java
|
||||
- richardwillis.vscode-gradle
|
||||
- vscjava.vscode-java-debug
|
||||
- vscode.github
|
||||
|
||||
tasks:
|
||||
- name: Setup pre-commit hool
|
||||
init: pre-commit install --config config/pre-commit/config.yml --allow-missing-config
|
||||
- name: Install npm packages
|
||||
init: npm ci
|
@ -19,6 +19,10 @@ process. When building on Windows, Use `gradlew.bat` instead of `./gradlew`.
|
||||
- **Clone the repository:** `git clone https://github.com/SquidDev-CC/CC-Tweaked.git && cd CC-Tweaked`
|
||||
- **Setup Forge:** `./gradlew build`
|
||||
- **Run Minecraft:** `./gradlew runClient` (or run the `GradleStart` class from your IDE).
|
||||
- **Optionally:** For small PRs (especially those only touching Lua code), it may be easier to use GitPod, which
|
||||
provides a pre-configured environment: [![Gitpod ready-to-code](https://img.shields.io/badge/Gitpod-ready--to--code-2b2b2b?logo=gitpod)](https://gitpod.io/#https://github.com/SquidDev-CC/CC-Tweaked/)
|
||||
|
||||
Do note you will need to download the mod after compiling to test.
|
||||
|
||||
If you want to run CC:T in a normal Minecraft instance, run `./gradlew build` and copy the `.jar` from `build/libs`.
|
||||
These commands may take a few minutes to run the first time, as the environment is set up, but should be much faster
|
||||
|
59
build.gradle
@ -1,6 +1,5 @@
|
||||
buildscript {
|
||||
repositories {
|
||||
jcenter()
|
||||
mavenCentral()
|
||||
maven {
|
||||
name = "forge"
|
||||
@ -14,7 +13,7 @@ buildscript {
|
||||
}
|
||||
dependencies {
|
||||
classpath 'com.google.code.gson:gson:2.8.1'
|
||||
classpath 'net.minecraftforge.gradle:ForgeGradle:4.1.9'
|
||||
classpath 'net.minecraftforge.gradle:ForgeGradle:5.0.12'
|
||||
classpath 'org.spongepowered:mixingradle:0.7-SNAPSHOT'
|
||||
}
|
||||
}
|
||||
@ -23,10 +22,11 @@ plugins {
|
||||
id "checkstyle"
|
||||
id "jacoco"
|
||||
id "maven-publish"
|
||||
id "com.github.hierynomus.license" version "0.15.0"
|
||||
id "com.github.hierynomus.license" version "0.16.1"
|
||||
id "com.matthewprenger.cursegradle" version "1.4.0"
|
||||
id "com.github.breadmoirai.github-release" version "2.2.12"
|
||||
id "org.jetbrains.kotlin.jvm" version "1.3.72"
|
||||
id "com.modrinth.minotaur" version "1.2.1"
|
||||
}
|
||||
|
||||
apply plugin: 'net.minecraftforge.gradle'
|
||||
@ -37,15 +37,30 @@ version = mod_version
|
||||
group = "org.squiddev"
|
||||
archivesBaseName = "cc-tweaked-${mc_version}"
|
||||
|
||||
def javaVersion = JavaLanguageVersion.of(8)
|
||||
java {
|
||||
toolchain {
|
||||
languageVersion = JavaLanguageVersion.of(8)
|
||||
languageVersion = javaVersion
|
||||
}
|
||||
|
||||
withSourcesJar()
|
||||
withJavadocJar()
|
||||
}
|
||||
|
||||
// Tragically java.toolchain.languageVersion doesn't apply to ForgeGradle's
|
||||
// tasks, so we force all launchers to use the right Java version.
|
||||
tasks.withType(JavaCompile).configureEach {
|
||||
javaCompiler = javaToolchains.compilerFor {
|
||||
languageVersion = javaVersion
|
||||
}
|
||||
}
|
||||
|
||||
tasks.withType(JavaExec).configureEach {
|
||||
javaLauncher = javaToolchains.launcherFor {
|
||||
languageVersion = javaVersion
|
||||
}
|
||||
}
|
||||
|
||||
minecraft {
|
||||
runs {
|
||||
client {
|
||||
@ -219,6 +234,7 @@ processResources {
|
||||
e.printStackTrace()
|
||||
}
|
||||
inputs.property "commithash", hash
|
||||
duplicatesStrategy = DuplicatesStrategy.INCLUDE
|
||||
|
||||
from(sourceSets.main.resources.srcDirs) {
|
||||
include 'META-INF/mods.toml'
|
||||
@ -235,6 +251,10 @@ processResources {
|
||||
}
|
||||
}
|
||||
|
||||
sourcesJar {
|
||||
duplicatesStrategy = DuplicatesStrategy.INCLUDE
|
||||
}
|
||||
|
||||
// Web tasks
|
||||
|
||||
|
||||
@ -416,31 +436,31 @@ task checkRelease {
|
||||
description "Verifies that everything is ready for a release"
|
||||
|
||||
inputs.property "version", mod_version
|
||||
inputs.file("src/main/resources/data/computercraft/lua/rom/help/changelog.txt")
|
||||
inputs.file("src/main/resources/data/computercraft/lua/rom/help/whatsnew.txt")
|
||||
inputs.file("src/main/resources/data/computercraft/lua/rom/help/changelog.md")
|
||||
inputs.file("src/main/resources/data/computercraft/lua/rom/help/whatsnew.md")
|
||||
|
||||
doLast {
|
||||
def ok = true
|
||||
|
||||
// Check we're targetting the current version
|
||||
def whatsnew = new File(projectDir, "src/main/resources/data/computercraft/lua/rom/help/whatsnew.txt").readLines()
|
||||
def whatsnew = new File(projectDir, "src/main/resources/data/computercraft/lua/rom/help/whatsnew.md").readLines()
|
||||
if (whatsnew[0] != "New features in CC: Tweaked $mod_version") {
|
||||
ok = false
|
||||
project.logger.error("Expected `whatsnew.txt' to target $mod_version.")
|
||||
project.logger.error("Expected `whatsnew.md' to target $mod_version.")
|
||||
}
|
||||
|
||||
// Check "read more" exists and trim it
|
||||
def idx = whatsnew.findIndexOf { it == 'Type "help changelog" to see the full version history.' }
|
||||
if (idx == -1) {
|
||||
ok = false
|
||||
project.logger.error("Must mention the changelog in whatsnew.txt")
|
||||
project.logger.error("Must mention the changelog in whatsnew.md")
|
||||
} else {
|
||||
whatsnew = whatsnew.getAt(0..<idx)
|
||||
}
|
||||
|
||||
// Check whatsnew and changelog match.
|
||||
def versionChangelog = "# " + whatsnew.join("\n")
|
||||
def changelog = new File(projectDir, "src/main/resources/data/computercraft/lua/rom/help/changelog.txt").getText()
|
||||
def changelog = new File(projectDir, "src/main/resources/data/computercraft/lua/rom/help/changelog.md").getText()
|
||||
if (!changelog.startsWith(versionChangelog)) {
|
||||
ok = false
|
||||
project.logger.error("whatsnew and changelog are not in sync")
|
||||
@ -464,6 +484,21 @@ curseforge {
|
||||
}
|
||||
}
|
||||
|
||||
import com.modrinth.minotaur.TaskModrinthUpload
|
||||
tasks.register('publishModrinth', TaskModrinthUpload.class).configure {
|
||||
dependsOn('assemble', 'reobfJar')
|
||||
onlyIf {
|
||||
project.hasProperty('modrinthApiKey')
|
||||
}
|
||||
|
||||
token = project.hasProperty('curseForgeApiKey')
|
||||
projectId = 'gu7yAYhd'
|
||||
versionNumber = project.mod_version
|
||||
uploadFile = jar
|
||||
addGameVersion(project.mc_version)
|
||||
addLoader('forge')
|
||||
}
|
||||
|
||||
tasks.withType(GenerateModuleMetadata) {
|
||||
// We can't generate metadata as that includes Forge as a dependency.
|
||||
enabled = false
|
||||
@ -538,10 +573,10 @@ githubRelease {
|
||||
prerelease false
|
||||
}
|
||||
|
||||
def uploadTasks = ["publish", "curseforge", "githubRelease"]
|
||||
def uploadTasks = ["publish", "curseforge", "publishModrinth", "githubRelease"]
|
||||
uploadTasks.forEach { tasks.getByName(it).dependsOn checkRelease }
|
||||
|
||||
task uploadAll(dependsOn: uploadTasks) {
|
||||
group "upload"
|
||||
description "Uploads to all repositories (Maven, Curse, GitHub release)"
|
||||
description "Uploads to all repositories (Maven, Curse, Modrinth, GitHub release)"
|
||||
}
|
||||
|
@ -7,9 +7,6 @@
|
||||
<suppress checks="StaticVariableName" files=".*[\\/]ComputerCraft.java" />
|
||||
<suppress checks="StaticVariableName" files=".*[\\/]ComputerCraftAPI.java" />
|
||||
|
||||
<!-- Do not check for missing package Javadoc. -->
|
||||
<suppress checks="JavadocStyle" files=".*[\\/]package-info.java" />
|
||||
|
||||
<!-- The commands API is documented in Lua. -->
|
||||
<suppress checks="SummaryJavadocCheck" files=".*[\\/]CommandAPI.java" />
|
||||
</suppressions>
|
||||
|
8
config/gitpod/Dockerfile
Normal file
@ -0,0 +1,8 @@
|
||||
FROM gitpod/workspace-base
|
||||
|
||||
USER gitpod
|
||||
|
||||
RUN sudo apt-get -q update \
|
||||
&& sudo apt-get install -yq openjdk-8-jdk openjdk-16-jdk python3-pip npm \
|
||||
&& sudo pip3 install pre-commit \
|
||||
&& sudo update-java-alternatives --set java-1.8.0-openjdk-amd64
|
2
gradle/wrapper/gradle-wrapper.properties
vendored
@ -1,5 +1,5 @@
|
||||
distributionBase=GRADLE_USER_HOME
|
||||
distributionPath=wrapper/dists
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-6.8.2-bin.zip
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-7.0-bin.zip
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
zipStorePath=wrapper/dists
|
||||
|
@ -32,7 +32,7 @@ public final class TransformedModel
|
||||
public TransformedModel( @Nonnull IBakedModel model )
|
||||
{
|
||||
this.model = Objects.requireNonNull( model );
|
||||
this.matrix = TransformationMatrix.identity();
|
||||
matrix = TransformationMatrix.identity();
|
||||
}
|
||||
|
||||
public static TransformedModel of( @Nonnull ModelResourceLocation location )
|
||||
|
@ -30,7 +30,7 @@ public class FileOperationException extends IOException
|
||||
public FileOperationException( @Nonnull String message )
|
||||
{
|
||||
super( Objects.requireNonNull( message, "message cannot be null" ) );
|
||||
this.filename = null;
|
||||
filename = null;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
|
@ -19,14 +19,14 @@ public class LuaException extends Exception
|
||||
public LuaException( @Nullable String message )
|
||||
{
|
||||
super( message );
|
||||
this.hasLevel = false;
|
||||
this.level = 1;
|
||||
hasLevel = false;
|
||||
level = 1;
|
||||
}
|
||||
|
||||
public LuaException( @Nullable String message, int level )
|
||||
{
|
||||
super( message );
|
||||
this.hasLevel = true;
|
||||
hasLevel = true;
|
||||
this.level = level;
|
||||
}
|
||||
|
||||
|
@ -30,14 +30,14 @@ public final class MethodResult
|
||||
|
||||
private MethodResult( Object[] arguments, ILuaCallback callback )
|
||||
{
|
||||
this.result = arguments;
|
||||
result = arguments;
|
||||
this.callback = callback;
|
||||
this.adjust = 0;
|
||||
adjust = 0;
|
||||
}
|
||||
|
||||
private MethodResult( Object[] arguments, ILuaCallback callback, int adjust )
|
||||
{
|
||||
this.result = arguments;
|
||||
result = arguments;
|
||||
this.callback = callback;
|
||||
this.adjust = adjust;
|
||||
}
|
||||
|
@ -22,6 +22,7 @@ public class ClientHooks
|
||||
if( event.getWorld().isClientSide() )
|
||||
{
|
||||
ClientMonitor.destroyAll();
|
||||
SoundManager.reset();
|
||||
}
|
||||
}
|
||||
|
||||
|
84
src/main/java/dan200/computercraft/client/SoundManager.java
Normal file
@ -0,0 +1,84 @@
|
||||
/*
|
||||
* This file is part of ComputerCraft - http://www.computercraft.info
|
||||
* Copyright Daniel Ratcliffe, 2011-2021. Do not distribute without permission.
|
||||
* Send enquiries to dratcliffe@gmail.com
|
||||
*/
|
||||
package dan200.computercraft.client;
|
||||
|
||||
import net.minecraft.client.Minecraft;
|
||||
import net.minecraft.client.audio.ISound;
|
||||
import net.minecraft.client.audio.ITickableSound;
|
||||
import net.minecraft.client.audio.LocatableSound;
|
||||
import net.minecraft.client.audio.SoundHandler;
|
||||
import net.minecraft.util.SoundCategory;
|
||||
import net.minecraft.util.SoundEvent;
|
||||
import net.minecraft.util.math.vector.Vector3d;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.UUID;
|
||||
|
||||
public class SoundManager
|
||||
{
|
||||
private static final Map<UUID, MoveableSound> sounds = new HashMap<>();
|
||||
|
||||
public static void playSound( UUID source, Vector3d position, SoundEvent event, float volume, float pitch )
|
||||
{
|
||||
SoundHandler soundManager = Minecraft.getInstance().getSoundManager();
|
||||
|
||||
MoveableSound oldSound = sounds.get( source );
|
||||
if( oldSound != null ) soundManager.stop( oldSound );
|
||||
|
||||
MoveableSound newSound = new MoveableSound( event, position, volume, pitch );
|
||||
sounds.put( source, newSound );
|
||||
soundManager.play( newSound );
|
||||
}
|
||||
|
||||
public static void stopSound( UUID source )
|
||||
{
|
||||
ISound sound = sounds.remove( source );
|
||||
if( sound == null ) return;
|
||||
|
||||
Minecraft.getInstance().getSoundManager().stop( sound );
|
||||
}
|
||||
|
||||
public static void moveSound( UUID source, Vector3d position )
|
||||
{
|
||||
MoveableSound sound = sounds.get( source );
|
||||
if( sound != null ) sound.setPosition( position );
|
||||
}
|
||||
|
||||
public static void reset()
|
||||
{
|
||||
sounds.clear();
|
||||
}
|
||||
|
||||
private static class MoveableSound extends LocatableSound implements ITickableSound
|
||||
{
|
||||
protected MoveableSound( SoundEvent sound, Vector3d position, float volume, float pitch )
|
||||
{
|
||||
super( sound, SoundCategory.RECORDS );
|
||||
setPosition( position );
|
||||
this.volume = volume;
|
||||
this.pitch = pitch;
|
||||
}
|
||||
|
||||
void setPosition( Vector3d position )
|
||||
{
|
||||
x = (float) position.x();
|
||||
y = (float) position.y();
|
||||
z = (float) position.z();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isStopped()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void tick()
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
@ -8,8 +8,8 @@ package dan200.computercraft.client.gui;
|
||||
import com.mojang.blaze3d.matrix.MatrixStack;
|
||||
import com.mojang.blaze3d.systems.RenderSystem;
|
||||
import dan200.computercraft.ComputerCraft;
|
||||
import dan200.computercraft.client.gui.widgets.ComputerSidebar;
|
||||
import dan200.computercraft.client.gui.widgets.WidgetTerminal;
|
||||
import dan200.computercraft.client.gui.widgets.WidgetWrapper;
|
||||
import dan200.computercraft.client.render.ComputerBorderRenderer;
|
||||
import dan200.computercraft.shared.computer.core.ClientComputer;
|
||||
import dan200.computercraft.shared.computer.core.ComputerFamily;
|
||||
@ -25,7 +25,6 @@ import org.lwjgl.glfw.GLFW;
|
||||
import javax.annotation.Nonnull;
|
||||
|
||||
import static dan200.computercraft.client.render.ComputerBorderRenderer.BORDER;
|
||||
import static dan200.computercraft.client.render.ComputerBorderRenderer.MARGIN;
|
||||
|
||||
public final class GuiComputer<T extends ContainerComputerBase> extends ContainerScreen<T>
|
||||
{
|
||||
@ -34,8 +33,7 @@ public final class GuiComputer<T extends ContainerComputerBase> extends Containe
|
||||
private final int termWidth;
|
||||
private final int termHeight;
|
||||
|
||||
private WidgetTerminal terminal;
|
||||
private WidgetWrapper terminalWrapper;
|
||||
private WidgetTerminal terminal = null;
|
||||
|
||||
private GuiComputer(
|
||||
T container, PlayerInventory player, ITextComponent title, int termWidth, int termHeight
|
||||
@ -46,7 +44,9 @@ public final class GuiComputer<T extends ContainerComputerBase> extends Containe
|
||||
computer = (ClientComputer) container.getComputer();
|
||||
this.termWidth = termWidth;
|
||||
this.termHeight = termHeight;
|
||||
terminal = null;
|
||||
|
||||
imageWidth = WidgetTerminal.getWidth( termWidth ) + BORDER * 2 + ComputerSidebar.WIDTH;
|
||||
imageHeight = WidgetTerminal.getHeight( termHeight ) + BORDER * 2;
|
||||
}
|
||||
|
||||
public static GuiComputer<ContainerComputer> create( ContainerComputer container, PlayerInventory inventory, ITextComponent component )
|
||||
@ -77,29 +77,21 @@ public final class GuiComputer<T extends ContainerComputerBase> extends Containe
|
||||
@Override
|
||||
protected void init()
|
||||
{
|
||||
minecraft.keyboardHandler.setSendRepeatsToGui( true );
|
||||
|
||||
int termPxWidth = termWidth * FixedWidthFontRenderer.FONT_WIDTH;
|
||||
int termPxHeight = termHeight * FixedWidthFontRenderer.FONT_HEIGHT;
|
||||
|
||||
imageWidth = termPxWidth + MARGIN * 2 + BORDER * 2;
|
||||
imageHeight = termPxHeight + MARGIN * 2 + BORDER * 2;
|
||||
|
||||
super.init();
|
||||
|
||||
terminal = new WidgetTerminal( minecraft, () -> computer, termWidth, termHeight, MARGIN, MARGIN, MARGIN, MARGIN );
|
||||
terminalWrapper = new WidgetWrapper( terminal, MARGIN + BORDER + leftPos, MARGIN + BORDER + topPos, termPxWidth, termPxHeight );
|
||||
minecraft.keyboardHandler.setSendRepeatsToGui( true );
|
||||
|
||||
children.add( terminalWrapper );
|
||||
setFocused( terminalWrapper );
|
||||
terminal = addButton( new WidgetTerminal( computer,
|
||||
leftPos + ComputerSidebar.WIDTH + BORDER, topPos + BORDER, termWidth, termHeight
|
||||
) );
|
||||
ComputerSidebar.addButtons( this, computer, this::addButton, leftPos, topPos + BORDER );
|
||||
setFocused( terminal );
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removed()
|
||||
{
|
||||
super.removed();
|
||||
children.remove( terminal );
|
||||
terminal = null;
|
||||
minecraft.keyboardHandler.setSendRepeatsToGui( false );
|
||||
}
|
||||
|
||||
@ -114,7 +106,7 @@ public final class GuiComputer<T extends ContainerComputerBase> extends Containe
|
||||
public boolean keyPressed( int key, int scancode, int modifiers )
|
||||
{
|
||||
// Forward the tab key to the terminal, rather than moving between controls.
|
||||
if( key == GLFW.GLFW_KEY_TAB && getFocused() != null && getFocused() == terminalWrapper )
|
||||
if( key == GLFW.GLFW_KEY_TAB && getFocused() != null && getFocused() == terminal )
|
||||
{
|
||||
return getFocused().keyPressed( key, scancode, modifiers );
|
||||
}
|
||||
@ -125,16 +117,11 @@ public final class GuiComputer<T extends ContainerComputerBase> extends Containe
|
||||
@Override
|
||||
public void renderBg( @Nonnull MatrixStack stack, float partialTicks, int mouseX, int mouseY )
|
||||
{
|
||||
// Draw terminal
|
||||
terminal.draw( terminalWrapper.getX(), terminalWrapper.getY() );
|
||||
|
||||
// Draw a border around the terminal
|
||||
RenderSystem.color4f( 1, 1, 1, 1 );
|
||||
minecraft.getTextureManager().bind( ComputerBorderRenderer.getTexture( family ) );
|
||||
ComputerBorderRenderer.render(
|
||||
terminalWrapper.getX() - MARGIN, terminalWrapper.getY() - MARGIN, getBlitOffset(),
|
||||
terminalWrapper.getWidth() + MARGIN * 2, terminalWrapper.getHeight() + MARGIN * 2
|
||||
);
|
||||
ComputerBorderRenderer.render( terminal.x, terminal.y, getBlitOffset(), terminal.getWidth(), terminal.getHeight() );
|
||||
ComputerSidebar.renderBackground( stack, leftPos, topPos + BORDER );
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -8,8 +8,9 @@ package dan200.computercraft.client.gui;
|
||||
import com.mojang.blaze3d.matrix.MatrixStack;
|
||||
import com.mojang.blaze3d.systems.RenderSystem;
|
||||
import dan200.computercraft.ComputerCraft;
|
||||
import dan200.computercraft.client.gui.widgets.ComputerSidebar;
|
||||
import dan200.computercraft.client.gui.widgets.WidgetTerminal;
|
||||
import dan200.computercraft.client.gui.widgets.WidgetWrapper;
|
||||
import dan200.computercraft.client.render.ComputerBorderRenderer;
|
||||
import dan200.computercraft.shared.computer.core.ClientComputer;
|
||||
import dan200.computercraft.shared.computer.core.ComputerFamily;
|
||||
import dan200.computercraft.shared.turtle.inventory.ContainerTurtle;
|
||||
@ -21,6 +22,8 @@ import org.lwjgl.glfw.GLFW;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
|
||||
import static dan200.computercraft.shared.turtle.inventory.ContainerTurtle.*;
|
||||
|
||||
public class GuiTurtle extends ContainerScreen<ContainerTurtle>
|
||||
{
|
||||
private static final ResourceLocation BACKGROUND_NORMAL = new ResourceLocation( ComputerCraft.MOD_ID, "textures/gui/turtle_normal.png" );
|
||||
@ -32,7 +35,6 @@ public class GuiTurtle extends ContainerScreen<ContainerTurtle>
|
||||
private final ClientComputer computer;
|
||||
|
||||
private WidgetTerminal terminal;
|
||||
private WidgetWrapper terminalWrapper;
|
||||
|
||||
public GuiTurtle( ContainerTurtle container, PlayerInventory player, ITextComponent title )
|
||||
{
|
||||
@ -52,27 +54,18 @@ public class GuiTurtle extends ContainerScreen<ContainerTurtle>
|
||||
super.init();
|
||||
minecraft.keyboardHandler.setSendRepeatsToGui( true );
|
||||
|
||||
int termPxWidth = ComputerCraft.turtleTermWidth * FixedWidthFontRenderer.FONT_WIDTH;
|
||||
int termPxHeight = ComputerCraft.turtleTermHeight * FixedWidthFontRenderer.FONT_HEIGHT;
|
||||
|
||||
terminal = new WidgetTerminal(
|
||||
minecraft, () -> computer,
|
||||
ComputerCraft.turtleTermWidth,
|
||||
ComputerCraft.turtleTermHeight,
|
||||
2, 2, 2, 2
|
||||
);
|
||||
terminalWrapper = new WidgetWrapper( terminal, 2 + 8 + leftPos, 2 + 8 + topPos, termPxWidth, termPxHeight );
|
||||
|
||||
children.add( terminalWrapper );
|
||||
setFocused( terminalWrapper );
|
||||
terminal = addButton( new WidgetTerminal(
|
||||
computer, leftPos + BORDER + ComputerSidebar.WIDTH, topPos + BORDER,
|
||||
ComputerCraft.turtleTermWidth, ComputerCraft.turtleTermHeight
|
||||
) );
|
||||
ComputerSidebar.addButtons( this, computer, this::addButton, leftPos, topPos + BORDER );
|
||||
setFocused( terminal );
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removed()
|
||||
{
|
||||
super.removed();
|
||||
children.remove( terminal );
|
||||
terminal = null;
|
||||
minecraft.keyboardHandler.setSendRepeatsToGui( false );
|
||||
}
|
||||
|
||||
@ -87,7 +80,7 @@ public class GuiTurtle extends ContainerScreen<ContainerTurtle>
|
||||
public boolean keyPressed( int key, int scancode, int modifiers )
|
||||
{
|
||||
// Forward the tab key to the terminal, rather than moving between controls.
|
||||
if( key == GLFW.GLFW_KEY_TAB && getFocused() != null && getFocused() == terminalWrapper )
|
||||
if( key == GLFW.GLFW_KEY_TAB && getFocused() != null && getFocused() == terminal )
|
||||
{
|
||||
return getFocused().keyPressed( key, scancode, modifiers );
|
||||
}
|
||||
@ -98,24 +91,21 @@ public class GuiTurtle extends ContainerScreen<ContainerTurtle>
|
||||
@Override
|
||||
protected void renderBg( @Nonnull MatrixStack transform, float partialTicks, int mouseX, int mouseY )
|
||||
{
|
||||
// Draw term
|
||||
ResourceLocation texture = family == ComputerFamily.ADVANCED ? BACKGROUND_ADVANCED : BACKGROUND_NORMAL;
|
||||
terminal.draw( terminalWrapper.getX(), terminalWrapper.getY() );
|
||||
boolean advanced = family == ComputerFamily.ADVANCED;
|
||||
minecraft.getTextureManager().bind( advanced ? BACKGROUND_ADVANCED : BACKGROUND_NORMAL );
|
||||
blit( transform, leftPos + ComputerSidebar.WIDTH, topPos, 0, 0, imageWidth, imageHeight );
|
||||
|
||||
// Draw border/inventory
|
||||
RenderSystem.color4f( 1.0F, 1.0F, 1.0F, 1.0F );
|
||||
minecraft.getTextureManager().bind( texture );
|
||||
blit( transform, leftPos, topPos, 0, 0, imageWidth, imageHeight );
|
||||
minecraft.getTextureManager().bind( advanced ? ComputerBorderRenderer.BACKGROUND_ADVANCED : ComputerBorderRenderer.BACKGROUND_NORMAL );
|
||||
ComputerSidebar.renderBackground( transform, leftPos, topPos + BORDER );
|
||||
|
||||
// Draw selection slot
|
||||
int slot = container.getSelectedSlot();
|
||||
if( slot >= 0 )
|
||||
{
|
||||
RenderSystem.color4f( 1.0F, 1.0F, 1.0F, 1.0F );
|
||||
int slotX = slot % 4;
|
||||
int slotY = slot / 4;
|
||||
blit( transform,
|
||||
leftPos + ContainerTurtle.TURTLE_START_X - 2 + slotX * 18,
|
||||
topPos + ContainerTurtle.PLAYER_START_Y - 2 + slotY * 18,
|
||||
leftPos + TURTLE_START_X - 2 + slotX * 18, topPos + PLAYER_START_Y - 2 + slotY * 18,
|
||||
0, 217, 24, 24
|
||||
);
|
||||
}
|
||||
|
@ -0,0 +1,106 @@
|
||||
/*
|
||||
* This file is part of ComputerCraft - http://www.computercraft.info
|
||||
* Copyright Daniel Ratcliffe, 2011-2021. Do not distribute without permission.
|
||||
* Send enquiries to dratcliffe@gmail.com
|
||||
*/
|
||||
package dan200.computercraft.client.gui.widgets;
|
||||
|
||||
import com.mojang.blaze3d.matrix.MatrixStack;
|
||||
import dan200.computercraft.ComputerCraft;
|
||||
import dan200.computercraft.client.render.ComputerBorderRenderer;
|
||||
import dan200.computercraft.shared.computer.core.ClientComputer;
|
||||
import net.minecraft.client.gui.screen.Screen;
|
||||
import net.minecraft.client.gui.widget.Widget;
|
||||
import net.minecraft.util.ResourceLocation;
|
||||
import net.minecraft.util.text.TextFormatting;
|
||||
import net.minecraft.util.text.TranslationTextComponent;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
/**
|
||||
* Registers buttons to interact with a computer.
|
||||
*/
|
||||
public final class ComputerSidebar
|
||||
{
|
||||
private static final ResourceLocation TEXTURE = new ResourceLocation( ComputerCraft.MOD_ID, "textures/gui/buttons.png" );
|
||||
|
||||
private static final int TEX_SIZE = 64;
|
||||
|
||||
private static final int ICON_WIDTH = 12;
|
||||
private static final int ICON_HEIGHT = 12;
|
||||
private static final int ICON_MARGIN = 2;
|
||||
|
||||
private static final int ICON_TEX_Y_DIFF = 14;
|
||||
|
||||
private static final int CORNERS_BORDER = 3;
|
||||
private static final int FULL_BORDER = CORNERS_BORDER + ICON_MARGIN;
|
||||
|
||||
private static final int BUTTONS = 2;
|
||||
private static final int HEIGHT = (ICON_HEIGHT + ICON_MARGIN * 2) * BUTTONS + CORNERS_BORDER * 2;
|
||||
public static final int WIDTH = 17;
|
||||
|
||||
private ComputerSidebar()
|
||||
{
|
||||
}
|
||||
|
||||
public static void addButtons( Screen screen, ClientComputer computer, Consumer<Widget> add, int x, int y )
|
||||
{
|
||||
x += CORNERS_BORDER + 1;
|
||||
y += CORNERS_BORDER + ICON_MARGIN;
|
||||
|
||||
add.accept( new DynamicImageButton(
|
||||
screen, x, y, ICON_WIDTH, ICON_HEIGHT, () -> computer.isOn() ? 15 : 1, 1, ICON_TEX_Y_DIFF,
|
||||
TEXTURE, TEX_SIZE, TEX_SIZE, b -> toggleComputer( computer ),
|
||||
() -> computer.isOn() ? Arrays.asList(
|
||||
new TranslationTextComponent( "gui.computercraft.tooltip.turn_off" ),
|
||||
new TranslationTextComponent( "gui.computercraft.tooltip.turn_off.key" ).withStyle( TextFormatting.GRAY )
|
||||
) : Arrays.asList(
|
||||
new TranslationTextComponent( "gui.computercraft.tooltip.turn_on" ),
|
||||
new TranslationTextComponent( "gui.computercraft.tooltip.turn_off.key" ).withStyle( TextFormatting.GRAY )
|
||||
)
|
||||
) );
|
||||
|
||||
y += ICON_HEIGHT + ICON_MARGIN * 2;
|
||||
|
||||
add.accept( new DynamicImageButton(
|
||||
screen, x, y, ICON_WIDTH, ICON_HEIGHT, 29, 1, ICON_TEX_Y_DIFF,
|
||||
TEXTURE, TEX_SIZE, TEX_SIZE, b -> computer.queueEvent( "terminate" ),
|
||||
Arrays.asList(
|
||||
new TranslationTextComponent( "gui.computercraft.tooltip.terminate" ),
|
||||
new TranslationTextComponent( "gui.computercraft.tooltip.terminate.key" ).withStyle( TextFormatting.GRAY )
|
||||
)
|
||||
) );
|
||||
}
|
||||
|
||||
public static void renderBackground( MatrixStack transform, int x, int y )
|
||||
{
|
||||
Screen.blit( transform,
|
||||
x, y, 0, 102, WIDTH, FULL_BORDER,
|
||||
ComputerBorderRenderer.TEX_SIZE, ComputerBorderRenderer.TEX_SIZE
|
||||
);
|
||||
|
||||
Screen.blit( transform,
|
||||
x, y + FULL_BORDER, WIDTH, HEIGHT - FULL_BORDER * 2,
|
||||
0, 107, WIDTH, 4,
|
||||
ComputerBorderRenderer.TEX_SIZE, ComputerBorderRenderer.TEX_SIZE
|
||||
);
|
||||
|
||||
Screen.blit( transform,
|
||||
x, y + HEIGHT - FULL_BORDER, 0, 111, WIDTH, FULL_BORDER,
|
||||
ComputerBorderRenderer.TEX_SIZE, ComputerBorderRenderer.TEX_SIZE
|
||||
);
|
||||
}
|
||||
|
||||
private static void toggleComputer( ClientComputer computer )
|
||||
{
|
||||
if( computer.isOn() )
|
||||
{
|
||||
computer.shutdown();
|
||||
}
|
||||
else
|
||||
{
|
||||
computer.turnOn();
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,101 @@
|
||||
/*
|
||||
* This file is part of ComputerCraft - http://www.computercraft.info
|
||||
* Copyright Daniel Ratcliffe, 2011-2021. Do not distribute without permission.
|
||||
* Send enquiries to dratcliffe@gmail.com
|
||||
*/
|
||||
package dan200.computercraft.client.gui.widgets;
|
||||
|
||||
import com.mojang.blaze3d.matrix.MatrixStack;
|
||||
import com.mojang.blaze3d.systems.RenderSystem;
|
||||
import net.minecraft.client.Minecraft;
|
||||
import net.minecraft.client.gui.screen.Screen;
|
||||
import net.minecraft.client.gui.widget.button.Button;
|
||||
import net.minecraft.util.ResourceLocation;
|
||||
import net.minecraft.util.text.ITextComponent;
|
||||
import net.minecraft.util.text.StringTextComponent;
|
||||
import net.minecraftforge.common.util.NonNullSupplier;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
import java.util.List;
|
||||
import java.util.function.IntSupplier;
|
||||
|
||||
/**
|
||||
* Version of {@link net.minecraft.client.gui.widget.button.ImageButton} which allows changing some properties
|
||||
* dynamically.
|
||||
*/
|
||||
public class DynamicImageButton extends Button
|
||||
{
|
||||
private final Screen screen;
|
||||
private final ResourceLocation texture;
|
||||
private final IntSupplier xTexStart;
|
||||
private final int yTexStart;
|
||||
private final int yDiffTex;
|
||||
private final int textureWidth;
|
||||
private final int textureHeight;
|
||||
private final NonNullSupplier<List<ITextComponent>> tooltip;
|
||||
|
||||
public DynamicImageButton(
|
||||
Screen screen, int x, int y, int width, int height, int xTexStart, int yTexStart, int yDiffTex,
|
||||
ResourceLocation texture, int textureWidth, int textureHeight,
|
||||
IPressable onPress, List<ITextComponent> tooltip
|
||||
)
|
||||
{
|
||||
this(
|
||||
screen, x, y, width, height, () -> xTexStart, yTexStart, yDiffTex,
|
||||
texture, textureWidth, textureHeight,
|
||||
onPress, () -> tooltip
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
public DynamicImageButton(
|
||||
Screen screen, int x, int y, int width, int height, IntSupplier xTexStart, int yTexStart, int yDiffTex,
|
||||
ResourceLocation texture, int textureWidth, int textureHeight,
|
||||
IPressable onPress, NonNullSupplier<List<ITextComponent>> tooltip
|
||||
)
|
||||
{
|
||||
super( x, y, width, height, StringTextComponent.EMPTY, onPress );
|
||||
this.screen = screen;
|
||||
this.textureWidth = textureWidth;
|
||||
this.textureHeight = textureHeight;
|
||||
this.xTexStart = xTexStart;
|
||||
this.yTexStart = yTexStart;
|
||||
this.yDiffTex = yDiffTex;
|
||||
this.texture = texture;
|
||||
this.tooltip = tooltip;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void renderButton( @Nonnull MatrixStack stack, int mouseX, int mouseY, float partialTicks )
|
||||
{
|
||||
Minecraft minecraft = Minecraft.getInstance();
|
||||
minecraft.getTextureManager().bind( texture );
|
||||
RenderSystem.disableDepthTest();
|
||||
|
||||
int yTex = yTexStart;
|
||||
if( isHovered() ) yTex += yDiffTex;
|
||||
|
||||
blit( stack, x, y, xTexStart.getAsInt(), yTex, width, height, textureWidth, textureHeight );
|
||||
RenderSystem.enableDepthTest();
|
||||
|
||||
if( isHovered() ) renderToolTip( stack, mouseX, mouseY );
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
@Override
|
||||
public ITextComponent getMessage()
|
||||
{
|
||||
List<ITextComponent> tooltip = this.tooltip.get();
|
||||
return tooltip.isEmpty() ? StringTextComponent.EMPTY : tooltip.get( 0 );
|
||||
}
|
||||
|
||||
@Override
|
||||
public void renderToolTip( @Nonnull MatrixStack stack, int mouseX, int mouseY )
|
||||
{
|
||||
List<ITextComponent> tooltip = this.tooltip.get();
|
||||
if( !tooltip.isEmpty() )
|
||||
{
|
||||
screen.renderWrappedToolTip( stack, tooltip, mouseX, mouseY, screen.getMinecraft().font );
|
||||
}
|
||||
}
|
||||
}
|
@ -5,32 +5,35 @@
|
||||
*/
|
||||
package dan200.computercraft.client.gui.widgets;
|
||||
|
||||
import com.mojang.blaze3d.matrix.MatrixStack;
|
||||
import dan200.computercraft.client.gui.FixedWidthFontRenderer;
|
||||
import dan200.computercraft.core.terminal.Terminal;
|
||||
import dan200.computercraft.shared.computer.core.ClientComputer;
|
||||
import dan200.computercraft.shared.computer.core.IComputer;
|
||||
import net.minecraft.client.Minecraft;
|
||||
import net.minecraft.client.gui.IGuiEventListener;
|
||||
import net.minecraft.client.gui.widget.Widget;
|
||||
import net.minecraft.util.SharedConstants;
|
||||
import net.minecraft.util.math.vector.Matrix4f;
|
||||
import net.minecraft.util.text.StringTextComponent;
|
||||
import org.lwjgl.glfw.GLFW;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
import java.util.BitSet;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
import static dan200.computercraft.client.gui.FixedWidthFontRenderer.FONT_HEIGHT;
|
||||
import static dan200.computercraft.client.gui.FixedWidthFontRenderer.FONT_WIDTH;
|
||||
import static dan200.computercraft.client.render.ComputerBorderRenderer.MARGIN;
|
||||
|
||||
public class WidgetTerminal implements IGuiEventListener
|
||||
public class WidgetTerminal extends Widget
|
||||
{
|
||||
private static final float TERMINATE_TIME = 0.5f;
|
||||
|
||||
private final Minecraft client;
|
||||
private final ClientComputer computer;
|
||||
|
||||
private boolean focused;
|
||||
|
||||
private final Supplier<ClientComputer> computer;
|
||||
private final int termWidth;
|
||||
private final int termHeight;
|
||||
// The positions of the actual terminal
|
||||
private final int innerX;
|
||||
private final int innerY;
|
||||
private final int innerWidth;
|
||||
private final int innerHeight;
|
||||
|
||||
private float terminateTimer = -1;
|
||||
private float rebootTimer = -1;
|
||||
@ -40,23 +43,18 @@ public class WidgetTerminal implements IGuiEventListener
|
||||
private int lastMouseX = -1;
|
||||
private int lastMouseY = -1;
|
||||
|
||||
private final int leftMargin;
|
||||
private final int rightMargin;
|
||||
private final int topMargin;
|
||||
private final int bottomMargin;
|
||||
|
||||
private final BitSet keysDown = new BitSet( 256 );
|
||||
|
||||
public WidgetTerminal( Minecraft client, Supplier<ClientComputer> computer, int termWidth, int termHeight, int leftMargin, int rightMargin, int topMargin, int bottomMargin )
|
||||
public WidgetTerminal( @Nonnull ClientComputer computer, int x, int y, int termWidth, int termHeight )
|
||||
{
|
||||
this.client = client;
|
||||
super( x, y, termWidth * FONT_WIDTH + MARGIN * 2, termHeight * FONT_HEIGHT + MARGIN * 2, StringTextComponent.EMPTY );
|
||||
|
||||
this.computer = computer;
|
||||
this.termWidth = termWidth;
|
||||
this.termHeight = termHeight;
|
||||
this.leftMargin = leftMargin;
|
||||
this.rightMargin = rightMargin;
|
||||
this.topMargin = topMargin;
|
||||
this.bottomMargin = bottomMargin;
|
||||
|
||||
innerX = x + MARGIN;
|
||||
innerY = y + MARGIN;
|
||||
innerWidth = termWidth * FONT_WIDTH;
|
||||
innerHeight = termHeight * FONT_HEIGHT;
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -65,7 +63,7 @@ public class WidgetTerminal implements IGuiEventListener
|
||||
if( ch >= 32 && ch <= 126 || ch >= 160 && ch <= 255 ) // printable chars in byte range
|
||||
{
|
||||
// Queue the "char" event
|
||||
queueEvent( "char", Character.toString( ch ) );
|
||||
computer.queueEvent( "char", new Object[] { Character.toString( ch ) } );
|
||||
}
|
||||
|
||||
return true;
|
||||
@ -91,7 +89,7 @@ public class WidgetTerminal implements IGuiEventListener
|
||||
|
||||
case GLFW.GLFW_KEY_V:
|
||||
// Ctrl+V for paste
|
||||
String clipboard = client.keyboardHandler.getClipboard();
|
||||
String clipboard = Minecraft.getInstance().keyboardHandler.getClipboard();
|
||||
if( clipboard != null )
|
||||
{
|
||||
// Clip to the first occurrence of \r or \n
|
||||
@ -116,7 +114,7 @@ public class WidgetTerminal implements IGuiEventListener
|
||||
{
|
||||
// Clip to 512 characters and queue the event
|
||||
if( clipboard.length() > 512 ) clipboard = clipboard.substring( 0, 512 );
|
||||
queueEvent( "paste", clipboard );
|
||||
computer.queueEvent( "paste", new Object[] { clipboard } );
|
||||
}
|
||||
|
||||
return true;
|
||||
@ -129,8 +127,7 @@ public class WidgetTerminal implements IGuiEventListener
|
||||
// Queue the "key" event and add to the down set
|
||||
boolean repeat = keysDown.get( key );
|
||||
keysDown.set( key );
|
||||
IComputer computer = this.computer.get();
|
||||
if( computer != null ) computer.keyDown( key, repeat );
|
||||
computer.keyDown( key, repeat );
|
||||
}
|
||||
|
||||
return true;
|
||||
@ -143,8 +140,7 @@ public class WidgetTerminal implements IGuiEventListener
|
||||
if( key >= 0 && keysDown.get( key ) )
|
||||
{
|
||||
keysDown.set( key, false );
|
||||
IComputer computer = this.computer.get();
|
||||
if( computer != null ) computer.keyUp( key );
|
||||
computer.keyUp( key );
|
||||
}
|
||||
|
||||
switch( key )
|
||||
@ -170,14 +166,14 @@ public class WidgetTerminal implements IGuiEventListener
|
||||
@Override
|
||||
public boolean mouseClicked( double mouseX, double mouseY, int button )
|
||||
{
|
||||
ClientComputer computer = this.computer.get();
|
||||
if( computer == null || !computer.isColour() || button < 0 || button > 2 ) return false;
|
||||
if( !inTermRegion( mouseX, mouseY ) ) return false;
|
||||
if( !computer.isColour() || button < 0 || button > 2 ) return false;
|
||||
|
||||
Terminal term = computer.getTerminal();
|
||||
if( term != null )
|
||||
{
|
||||
int charX = (int) (mouseX / FONT_WIDTH);
|
||||
int charY = (int) (mouseY / FONT_HEIGHT);
|
||||
int charX = (int) ((mouseX - x) / FONT_WIDTH);
|
||||
int charY = (int) ((mouseY - x) / FONT_HEIGHT);
|
||||
charX = Math.min( Math.max( charX, 0 ), term.getWidth() - 1 );
|
||||
charY = Math.min( Math.max( charY, 0 ), term.getHeight() - 1 );
|
||||
|
||||
@ -194,14 +190,14 @@ public class WidgetTerminal implements IGuiEventListener
|
||||
@Override
|
||||
public boolean mouseReleased( double mouseX, double mouseY, int button )
|
||||
{
|
||||
ClientComputer computer = this.computer.get();
|
||||
if( computer == null || !computer.isColour() || button < 0 || button > 2 ) return false;
|
||||
if( !inTermRegion( mouseX, mouseY ) ) return false;
|
||||
if( !computer.isColour() || button < 0 || button > 2 ) return false;
|
||||
|
||||
Terminal term = computer.getTerminal();
|
||||
if( term != null )
|
||||
{
|
||||
int charX = (int) (mouseX / FONT_WIDTH);
|
||||
int charY = (int) (mouseY / FONT_HEIGHT);
|
||||
int charX = (int) ((mouseX - x) / FONT_WIDTH);
|
||||
int charY = (int) ((mouseY - x) / FONT_HEIGHT);
|
||||
charX = Math.min( Math.max( charX, 0 ), term.getWidth() - 1 );
|
||||
charY = Math.min( Math.max( charY, 0 ), term.getHeight() - 1 );
|
||||
|
||||
@ -221,14 +217,14 @@ public class WidgetTerminal implements IGuiEventListener
|
||||
@Override
|
||||
public boolean mouseDragged( double mouseX, double mouseY, int button, double v2, double v3 )
|
||||
{
|
||||
ClientComputer computer = this.computer.get();
|
||||
if( computer == null || !computer.isColour() || button < 0 || button > 2 ) return false;
|
||||
if( !inTermRegion( mouseX, mouseY ) ) return false;
|
||||
if( !computer.isColour() || button < 0 || button > 2 ) return false;
|
||||
|
||||
Terminal term = computer.getTerminal();
|
||||
if( term != null )
|
||||
{
|
||||
int charX = (int) (mouseX / FONT_WIDTH);
|
||||
int charY = (int) (mouseY / FONT_HEIGHT);
|
||||
int charX = (int) ((mouseX - x) / FONT_WIDTH);
|
||||
int charY = (int) ((mouseY - x) / FONT_HEIGHT);
|
||||
charX = Math.min( Math.max( charX, 0 ), term.getWidth() - 1 );
|
||||
charY = Math.min( Math.max( charY, 0 ), term.getHeight() - 1 );
|
||||
|
||||
@ -246,14 +242,14 @@ public class WidgetTerminal implements IGuiEventListener
|
||||
@Override
|
||||
public boolean mouseScrolled( double mouseX, double mouseY, double delta )
|
||||
{
|
||||
ClientComputer computer = this.computer.get();
|
||||
if( computer == null || !computer.isColour() || delta == 0 ) return false;
|
||||
if( !inTermRegion( mouseX, mouseY ) ) return false;
|
||||
if( !computer.isColour() || delta == 0 ) return false;
|
||||
|
||||
Terminal term = computer.getTerminal();
|
||||
if( term != null )
|
||||
{
|
||||
int charX = (int) (mouseX / FONT_WIDTH);
|
||||
int charY = (int) (mouseY / FONT_HEIGHT);
|
||||
int charX = (int) ((mouseX - innerX) / FONT_WIDTH);
|
||||
int charY = (int) ((mouseY - innerY) / FONT_HEIGHT);
|
||||
charX = Math.min( Math.max( charX, 0 ), term.getWidth() - 1 );
|
||||
charY = Math.min( Math.max( charY, 0 ), term.getHeight() - 1 );
|
||||
|
||||
@ -266,89 +262,74 @@ public class WidgetTerminal implements IGuiEventListener
|
||||
return true;
|
||||
}
|
||||
|
||||
private boolean inTermRegion( double mouseX, double mouseY )
|
||||
{
|
||||
return active && visible && mouseX >= innerX && mouseY >= innerY && mouseX < innerX + innerWidth && mouseY < innerY + innerHeight;
|
||||
}
|
||||
|
||||
public void update()
|
||||
{
|
||||
if( terminateTimer >= 0 && terminateTimer < TERMINATE_TIME && (terminateTimer += 0.05f) > TERMINATE_TIME )
|
||||
{
|
||||
queueEvent( "terminate" );
|
||||
computer.queueEvent( "terminate" );
|
||||
}
|
||||
|
||||
if( shutdownTimer >= 0 && shutdownTimer < TERMINATE_TIME && (shutdownTimer += 0.05f) > TERMINATE_TIME )
|
||||
{
|
||||
ClientComputer computer = this.computer.get();
|
||||
if( computer != null ) computer.shutdown();
|
||||
computer.shutdown();
|
||||
}
|
||||
|
||||
if( rebootTimer >= 0 && rebootTimer < TERMINATE_TIME && (rebootTimer += 0.05f) > TERMINATE_TIME )
|
||||
{
|
||||
ClientComputer computer = this.computer.get();
|
||||
if( computer != null ) computer.reboot();
|
||||
computer.reboot();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean changeFocus( boolean reversed )
|
||||
public void onFocusedChanged( boolean focused )
|
||||
{
|
||||
if( focused )
|
||||
if( !focused )
|
||||
{
|
||||
// When blurring, we should make all keys go up
|
||||
for( int key = 0; key < keysDown.size(); key++ )
|
||||
{
|
||||
if( keysDown.get( key ) ) queueEvent( "key_up", key );
|
||||
if( keysDown.get( key ) ) computer.keyUp( key );
|
||||
}
|
||||
keysDown.clear();
|
||||
|
||||
// When blurring, we should make the last mouse button go up
|
||||
if( lastMouseButton > 0 )
|
||||
{
|
||||
IComputer computer = this.computer.get();
|
||||
if( computer != null ) computer.mouseUp( lastMouseButton + 1, lastMouseX + 1, lastMouseY + 1 );
|
||||
computer.mouseUp( lastMouseButton + 1, lastMouseX + 1, lastMouseY + 1 );
|
||||
lastMouseButton = -1;
|
||||
}
|
||||
|
||||
shutdownTimer = terminateTimer = rebootTimer = -1;
|
||||
}
|
||||
focused = !focused;
|
||||
return true;
|
||||
}
|
||||
|
||||
public void draw( int originX, int originY )
|
||||
{
|
||||
synchronized( computer )
|
||||
{
|
||||
// Draw the screen contents
|
||||
ClientComputer computer = this.computer.get();
|
||||
Terminal terminal = computer != null ? computer.getTerminal() : null;
|
||||
if( terminal != null )
|
||||
{
|
||||
FixedWidthFontRenderer.drawTerminal( originX, originY, terminal, !computer.isColour(), topMargin, bottomMargin, leftMargin, rightMargin );
|
||||
}
|
||||
else
|
||||
{
|
||||
FixedWidthFontRenderer.drawEmptyTerminal(
|
||||
originX - leftMargin, originY - rightMargin,
|
||||
termWidth * FONT_WIDTH + leftMargin + rightMargin,
|
||||
termHeight * FONT_HEIGHT + topMargin + bottomMargin
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void queueEvent( String event )
|
||||
{
|
||||
ClientComputer computer = this.computer.get();
|
||||
if( computer != null ) computer.queueEvent( event );
|
||||
}
|
||||
|
||||
private void queueEvent( String event, Object... args )
|
||||
{
|
||||
ClientComputer computer = this.computer.get();
|
||||
if( computer != null ) computer.queueEvent( event, args );
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isMouseOver( double x, double y )
|
||||
public void render( @Nonnull MatrixStack transform, int mouseX, int mouseY, float partialTicks )
|
||||
{
|
||||
return true;
|
||||
Matrix4f matrix = transform.last().pose();
|
||||
Terminal terminal = computer.getTerminal();
|
||||
if( terminal != null )
|
||||
{
|
||||
FixedWidthFontRenderer.drawTerminal( matrix, innerX, innerY, terminal, !computer.isColour(), MARGIN, MARGIN, MARGIN, MARGIN );
|
||||
}
|
||||
else
|
||||
{
|
||||
FixedWidthFontRenderer.drawEmptyTerminal( matrix, x, y, width, height );
|
||||
}
|
||||
}
|
||||
|
||||
public static int getWidth( int termWidth )
|
||||
{
|
||||
return termWidth * FONT_WIDTH + MARGIN * 2;
|
||||
}
|
||||
|
||||
public static int getHeight( int termHeight )
|
||||
{
|
||||
return termHeight * FONT_HEIGHT + MARGIN * 2;
|
||||
}
|
||||
}
|
||||
|
@ -1,105 +0,0 @@
|
||||
/*
|
||||
* This file is part of ComputerCraft - http://www.computercraft.info
|
||||
* Copyright Daniel Ratcliffe, 2011-2021. Do not distribute without permission.
|
||||
* Send enquiries to dratcliffe@gmail.com
|
||||
*/
|
||||
package dan200.computercraft.client.gui.widgets;
|
||||
|
||||
import net.minecraft.client.gui.IGuiEventListener;
|
||||
|
||||
public class WidgetWrapper implements IGuiEventListener
|
||||
{
|
||||
private final IGuiEventListener listener;
|
||||
private final int x;
|
||||
private final int y;
|
||||
private final int width;
|
||||
private final int height;
|
||||
|
||||
public WidgetWrapper( IGuiEventListener listener, int x, int y, int width, int height )
|
||||
{
|
||||
this.listener = listener;
|
||||
this.x = x;
|
||||
this.y = y;
|
||||
this.width = width;
|
||||
this.height = height;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean changeFocus( boolean b )
|
||||
{
|
||||
return listener.changeFocus( b );
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean mouseClicked( double x, double y, int button )
|
||||
{
|
||||
double dx = x - this.x, dy = y - this.y;
|
||||
return dx >= 0 && dx < width && dy >= 0 && dy < height && listener.mouseClicked( dx, dy, button );
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean mouseReleased( double x, double y, int button )
|
||||
{
|
||||
double dx = x - this.x, dy = y - this.y;
|
||||
return dx >= 0 && dx < width && dy >= 0 && dy < height && listener.mouseReleased( dx, dy, button );
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean mouseDragged( double x, double y, int button, double deltaX, double deltaY )
|
||||
{
|
||||
double dx = x - this.x, dy = y - this.y;
|
||||
return dx >= 0 && dx < width && dy >= 0 && dy < height && listener.mouseDragged( dx, dy, button, deltaX, deltaY );
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean mouseScrolled( double x, double y, double delta )
|
||||
{
|
||||
double dx = x - this.x, dy = y - this.y;
|
||||
return dx >= 0 && dx < width && dy >= 0 && dy < height && listener.mouseScrolled( dx, dy, delta );
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean keyPressed( int key, int scancode, int modifiers )
|
||||
{
|
||||
return listener.keyPressed( key, scancode, modifiers );
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean keyReleased( int key, int scancode, int modifiers )
|
||||
{
|
||||
return listener.keyReleased( key, scancode, modifiers );
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean charTyped( char character, int modifiers )
|
||||
{
|
||||
return listener.charTyped( character, modifiers );
|
||||
}
|
||||
|
||||
public int getX()
|
||||
{
|
||||
return x;
|
||||
}
|
||||
|
||||
public int getY()
|
||||
{
|
||||
return y;
|
||||
}
|
||||
|
||||
public int getWidth()
|
||||
{
|
||||
return width;
|
||||
}
|
||||
|
||||
public int getHeight()
|
||||
{
|
||||
return height;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isMouseOver( double x, double y )
|
||||
{
|
||||
double dx = x - this.x, dy = y - this.y;
|
||||
return dx >= 0 && dx < width && dy >= 0 && dy < height;
|
||||
}
|
||||
}
|
@ -52,7 +52,8 @@ public class ComputerBorderRenderer
|
||||
|
||||
public static final int LIGHT_HEIGHT = 8;
|
||||
|
||||
private static final float TEX_SCALE = 1 / 256.0f;
|
||||
public static final int TEX_SIZE = 256;
|
||||
private static final float TEX_SCALE = 1 / (float) TEX_SIZE;
|
||||
|
||||
private final Matrix4f transform;
|
||||
private final IVertexBuilder builder;
|
||||
|
@ -37,7 +37,7 @@ public class HTTPAPI implements ILuaAPI
|
||||
{
|
||||
private final IAPIEnvironment apiEnvironment;
|
||||
|
||||
private final ResourceGroup<CheckUrl> checkUrls = new ResourceGroup<>();
|
||||
private final ResourceGroup<CheckUrl> checkUrls = new ResourceGroup<>( ResourceGroup.DEFAULT );
|
||||
private final ResourceGroup<HttpRequest> requests = new ResourceQueue<>( () -> ComputerCraft.httpMaxRequests );
|
||||
private final ResourceGroup<Websocket> websockets = new ResourceGroup<>( () -> ComputerCraft.httpMaxWebsockets );
|
||||
|
||||
@ -127,7 +127,10 @@ public class HTTPAPI implements ILuaAPI
|
||||
HttpRequest request = new HttpRequest( requests, apiEnvironment, address, postString, headers, binary, redirect );
|
||||
|
||||
// Make the request
|
||||
request.queue( r -> r.request( uri, httpMethod ) );
|
||||
if( !request.queue( r -> r.request( uri, httpMethod ) ) )
|
||||
{
|
||||
throw new LuaException( "Too many ongoing HTTP requests" );
|
||||
}
|
||||
|
||||
return new Object[] { true };
|
||||
}
|
||||
@ -138,12 +141,15 @@ public class HTTPAPI implements ILuaAPI
|
||||
}
|
||||
|
||||
@LuaFunction
|
||||
public final Object[] checkURL( String address )
|
||||
public final Object[] checkURL( String address ) throws LuaException
|
||||
{
|
||||
try
|
||||
{
|
||||
URI uri = HttpRequest.checkUri( address );
|
||||
new CheckUrl( checkUrls, apiEnvironment, address, uri ).queue( CheckUrl::run );
|
||||
if( !new CheckUrl( checkUrls, apiEnvironment, address, uri ).queue( CheckUrl::run ) )
|
||||
{
|
||||
throw new LuaException( "Too many ongoing checkUrl calls" );
|
||||
}
|
||||
|
||||
return new Object[] { true };
|
||||
}
|
||||
|
@ -97,7 +97,7 @@ public abstract class Resource<T extends Resource<T>> implements Closeable
|
||||
tryClose();
|
||||
}
|
||||
|
||||
public boolean queue( Consumer<T> task )
|
||||
public final boolean queue( Consumer<T> task )
|
||||
{
|
||||
@SuppressWarnings( "unchecked" )
|
||||
T thisT = (T) this;
|
||||
|
@ -18,6 +18,9 @@ import java.util.function.Supplier;
|
||||
*/
|
||||
public class ResourceGroup<T extends Resource<T>>
|
||||
{
|
||||
public static final int DEFAULT_LIMIT = 512;
|
||||
public static final IntSupplier DEFAULT = () -> DEFAULT_LIMIT;
|
||||
|
||||
private static final IntSupplier ZERO = () -> 0;
|
||||
|
||||
final IntSupplier limit;
|
||||
|
@ -38,8 +38,10 @@ public class ResourceQueue<T extends Resource<T>> extends ResourceGroup<T>
|
||||
public synchronized boolean queue( Supplier<T> resource )
|
||||
{
|
||||
if( !active ) return false;
|
||||
if( super.queue( resource ) ) return true;
|
||||
if( pending.size() > DEFAULT_LIMIT ) return false;
|
||||
|
||||
if( !super.queue( resource ) ) pending.add( resource );
|
||||
pending.add( resource );
|
||||
return true;
|
||||
}
|
||||
|
||||
|
@ -11,7 +11,10 @@ import com.google.common.cache.LoadingCache;
|
||||
import com.google.common.primitives.Primitives;
|
||||
import com.google.common.reflect.TypeToken;
|
||||
import dan200.computercraft.ComputerCraft;
|
||||
import dan200.computercraft.api.lua.*;
|
||||
import dan200.computercraft.api.lua.IArguments;
|
||||
import dan200.computercraft.api.lua.LuaException;
|
||||
import dan200.computercraft.api.lua.LuaFunction;
|
||||
import dan200.computercraft.api.lua.MethodResult;
|
||||
import org.objectweb.asm.ClassWriter;
|
||||
import org.objectweb.asm.MethodVisitor;
|
||||
import org.objectweb.asm.Type;
|
||||
@ -63,7 +66,7 @@ public final class Generator<T>
|
||||
{
|
||||
this.base = base;
|
||||
this.context = context;
|
||||
this.interfaces = new String[] { Type.getInternalName( base ) };
|
||||
interfaces = new String[] { Type.getInternalName( base ) };
|
||||
this.wrap = wrap;
|
||||
|
||||
StringBuilder methodDesc = new StringBuilder().append( "(Ljava/lang/Object;" );
|
||||
|
@ -251,6 +251,20 @@ final class ComputerExecutor
|
||||
* and then schedule a shutdown.
|
||||
*/
|
||||
void abort()
|
||||
{
|
||||
immediateFail( StateCommand.ABORT );
|
||||
}
|
||||
|
||||
/**
|
||||
* Abort this whole computer due to an internal error. This will immediately destroy the Lua machine,
|
||||
* and then schedule a shutdown.
|
||||
*/
|
||||
void fastFail()
|
||||
{
|
||||
immediateFail( StateCommand.ERROR );
|
||||
}
|
||||
|
||||
private void immediateFail( StateCommand command )
|
||||
{
|
||||
ILuaMachine machine = this.machine;
|
||||
if( machine != null ) machine.close();
|
||||
@ -258,7 +272,7 @@ final class ComputerExecutor
|
||||
synchronized( queueLock )
|
||||
{
|
||||
if( closed ) return;
|
||||
command = StateCommand.ABORT;
|
||||
this.command = command;
|
||||
if( isOn ) enqueue();
|
||||
}
|
||||
}
|
||||
@ -596,6 +610,12 @@ final class ComputerExecutor
|
||||
displayFailure( "Error running computer", TimeoutState.ABORT_MESSAGE );
|
||||
shutdown();
|
||||
break;
|
||||
|
||||
case ERROR:
|
||||
if( !isOn ) return;
|
||||
displayFailure( "Error running computer", "An internal error occurred, see logs." );
|
||||
shutdown();
|
||||
break;
|
||||
}
|
||||
}
|
||||
else if( event != null )
|
||||
@ -644,6 +664,7 @@ final class ComputerExecutor
|
||||
SHUTDOWN,
|
||||
REBOOT,
|
||||
ABORT,
|
||||
ERROR,
|
||||
}
|
||||
|
||||
private static final class Event
|
||||
|
@ -506,6 +506,8 @@ public final class ComputerThread
|
||||
catch( Exception | LinkageError | VirtualMachineError e )
|
||||
{
|
||||
ComputerCraft.log.error( "Error running task on computer #" + executor.getComputer().getID(), e );
|
||||
// Tear down the computer immediately. There's no guarantee it's well behaved from now on.
|
||||
executor.fastFail();
|
||||
}
|
||||
finally
|
||||
{
|
||||
|
@ -10,7 +10,6 @@ import dan200.computercraft.api.lua.*;
|
||||
import dan200.computercraft.core.asm.LuaMethod;
|
||||
import dan200.computercraft.core.asm.ObjectSource;
|
||||
import dan200.computercraft.core.computer.Computer;
|
||||
import dan200.computercraft.core.computer.MainThread;
|
||||
import dan200.computercraft.core.computer.TimeoutState;
|
||||
import dan200.computercraft.core.tracking.Tracking;
|
||||
import dan200.computercraft.core.tracking.TrackingField;
|
||||
@ -53,7 +52,7 @@ public class CobaltLuaMachine implements ILuaMachine
|
||||
private final Computer computer;
|
||||
private final TimeoutState timeout;
|
||||
private final TimeoutDebugHandler debug;
|
||||
private final ILuaContext context = new CobaltLuaContext();
|
||||
private final ILuaContext context;
|
||||
|
||||
private LuaState state;
|
||||
private LuaTable globals;
|
||||
@ -65,6 +64,7 @@ public class CobaltLuaMachine implements ILuaMachine
|
||||
{
|
||||
this.computer = computer;
|
||||
this.timeout = timeout;
|
||||
context = new LuaContext( computer );
|
||||
debug = new TimeoutDebugHandler();
|
||||
|
||||
// Create an environment to run in
|
||||
@ -509,53 +509,6 @@ public class CobaltLuaMachine implements ILuaMachine
|
||||
}
|
||||
}
|
||||
|
||||
private class CobaltLuaContext implements ILuaContext
|
||||
{
|
||||
@Override
|
||||
public long issueMainThreadTask( @Nonnull final ILuaTask task ) throws LuaException
|
||||
{
|
||||
// Issue command
|
||||
final long taskID = MainThread.getUniqueTaskID();
|
||||
final Runnable iTask = () -> {
|
||||
try
|
||||
{
|
||||
Object[] results = task.execute();
|
||||
if( results != null )
|
||||
{
|
||||
Object[] eventArguments = new Object[results.length + 2];
|
||||
eventArguments[0] = taskID;
|
||||
eventArguments[1] = true;
|
||||
System.arraycopy( results, 0, eventArguments, 2, results.length );
|
||||
computer.queueEvent( "task_complete", eventArguments );
|
||||
}
|
||||
else
|
||||
{
|
||||
computer.queueEvent( "task_complete", new Object[] { taskID, true } );
|
||||
}
|
||||
}
|
||||
catch( LuaException e )
|
||||
{
|
||||
computer.queueEvent( "task_complete", new Object[] { taskID, false, e.getMessage() } );
|
||||
}
|
||||
catch( Throwable t )
|
||||
{
|
||||
if( ComputerCraft.logComputerErrors ) ComputerCraft.log.error( "Error running task", t );
|
||||
computer.queueEvent( "task_complete", new Object[] {
|
||||
taskID, false, "Java Exception Thrown: " + t,
|
||||
} );
|
||||
}
|
||||
};
|
||||
if( computer.queueMainThread( iTask ) )
|
||||
{
|
||||
return taskID;
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new LuaException( "Task limit exceeded" );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static final class HardAbortError extends Error
|
||||
{
|
||||
private static final long serialVersionUID = 7954092008586367501L;
|
||||
|
69
src/main/java/dan200/computercraft/core/lua/LuaContext.java
Normal file
@ -0,0 +1,69 @@
|
||||
/*
|
||||
* This file is part of ComputerCraft - http://www.computercraft.info
|
||||
* Copyright Daniel Ratcliffe, 2011-2021. Do not distribute without permission.
|
||||
* Send enquiries to dratcliffe@gmail.com
|
||||
*/
|
||||
package dan200.computercraft.core.lua;
|
||||
|
||||
import dan200.computercraft.ComputerCraft;
|
||||
import dan200.computercraft.api.lua.ILuaContext;
|
||||
import dan200.computercraft.api.lua.ILuaTask;
|
||||
import dan200.computercraft.api.lua.LuaException;
|
||||
import dan200.computercraft.core.computer.Computer;
|
||||
import dan200.computercraft.core.computer.MainThread;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
|
||||
class LuaContext implements ILuaContext
|
||||
{
|
||||
private final Computer computer;
|
||||
|
||||
LuaContext( Computer computer )
|
||||
{
|
||||
this.computer = computer;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long issueMainThreadTask( @Nonnull final ILuaTask task ) throws LuaException
|
||||
{
|
||||
// Issue command
|
||||
final long taskID = MainThread.getUniqueTaskID();
|
||||
final Runnable iTask = () -> {
|
||||
try
|
||||
{
|
||||
Object[] results = task.execute();
|
||||
if( results != null )
|
||||
{
|
||||
Object[] eventArguments = new Object[results.length + 2];
|
||||
eventArguments[0] = taskID;
|
||||
eventArguments[1] = true;
|
||||
System.arraycopy( results, 0, eventArguments, 2, results.length );
|
||||
computer.queueEvent( "task_complete", eventArguments );
|
||||
}
|
||||
else
|
||||
{
|
||||
computer.queueEvent( "task_complete", new Object[] { taskID, true } );
|
||||
}
|
||||
}
|
||||
catch( LuaException e )
|
||||
{
|
||||
computer.queueEvent( "task_complete", new Object[] { taskID, false, e.getMessage() } );
|
||||
}
|
||||
catch( Exception t )
|
||||
{
|
||||
if( ComputerCraft.logComputerErrors ) ComputerCraft.log.error( "Error running task", t );
|
||||
computer.queueEvent( "task_complete", new Object[] {
|
||||
taskID, false, "Java Exception Thrown: " + t,
|
||||
} );
|
||||
}
|
||||
};
|
||||
if( computer.queueMainThread( iTask ) )
|
||||
{
|
||||
return taskID;
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new LuaException( "Task limit exceeded" );
|
||||
}
|
||||
}
|
||||
}
|
@ -44,9 +44,9 @@ public class Terminal
|
||||
this.height = height;
|
||||
onChanged = changedCallback;
|
||||
|
||||
text = new TextBuffer[this.height];
|
||||
textColour = new TextBuffer[this.height];
|
||||
backgroundColour = new TextBuffer[this.height];
|
||||
text = new TextBuffer[height];
|
||||
textColour = new TextBuffer[height];
|
||||
backgroundColour = new TextBuffer[height];
|
||||
for( int i = 0; i < this.height; i++ )
|
||||
{
|
||||
text[i] = new TextBuffer( ' ', this.width );
|
||||
@ -93,9 +93,9 @@ public class Terminal
|
||||
this.width = width;
|
||||
this.height = height;
|
||||
|
||||
text = new TextBuffer[this.height];
|
||||
textColour = new TextBuffer[this.height];
|
||||
backgroundColour = new TextBuffer[this.height];
|
||||
text = new TextBuffer[height];
|
||||
textColour = new TextBuffer[height];
|
||||
backgroundColour = new TextBuffer[height];
|
||||
for( int i = 0; i < this.height; i++ )
|
||||
{
|
||||
if( i >= oldHeight )
|
||||
|
@ -12,7 +12,7 @@ public class TextBuffer
|
||||
public TextBuffer( char c, int length )
|
||||
{
|
||||
text = new char[length];
|
||||
this.fill( c );
|
||||
fill( c );
|
||||
}
|
||||
|
||||
public TextBuffer( String text )
|
||||
@ -79,6 +79,7 @@ public class TextBuffer
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString()
|
||||
{
|
||||
return new String( text );
|
||||
|
@ -6,11 +6,11 @@
|
||||
package dan200.computercraft.data;
|
||||
|
||||
import dan200.computercraft.ComputerCraft;
|
||||
import dan200.computercraft.shared.CommonHooks;
|
||||
import dan200.computercraft.shared.Registry;
|
||||
import dan200.computercraft.shared.data.BlockNamedEntityLootCondition;
|
||||
import dan200.computercraft.shared.data.HasComputerIdLootCondition;
|
||||
import dan200.computercraft.shared.data.PlayerCreativeLootCondition;
|
||||
import dan200.computercraft.shared.CommonHooks;
|
||||
import net.minecraft.block.Block;
|
||||
import net.minecraft.data.DataGenerator;
|
||||
import net.minecraft.loot.*;
|
||||
|
@ -33,9 +33,9 @@ public final class TurtleUpgrades
|
||||
Wrapper( ITurtleUpgrade upgrade )
|
||||
{
|
||||
this.upgrade = upgrade;
|
||||
this.id = upgrade.getUpgradeID().toString();
|
||||
this.modId = ModLoadingContext.get().getActiveNamespace();
|
||||
this.enabled = true;
|
||||
id = upgrade.getUpgradeID().toString();
|
||||
modId = ModLoadingContext.get().getActiveNamespace();
|
||||
enabled = true;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -61,7 +61,7 @@ public class ContainerHeldItem extends Container
|
||||
public Factory( ContainerType<ContainerHeldItem> type, ItemStack stack, Hand hand )
|
||||
{
|
||||
this.type = type;
|
||||
this.name = stack.getHoverName();
|
||||
name = stack.getHoverName();
|
||||
this.hand = hand;
|
||||
}
|
||||
|
||||
|
@ -25,14 +25,14 @@ public class ContainerViewComputer extends ContainerComputerBase implements ICon
|
||||
public ContainerViewComputer( int id, ServerComputer computer )
|
||||
{
|
||||
super( Registry.ModContainers.VIEW_COMPUTER.get(), id, player -> canInteractWith( computer, player ), computer, computer.getFamily() );
|
||||
this.width = this.height = 0;
|
||||
width = height = 0;
|
||||
}
|
||||
|
||||
public ContainerViewComputer( int id, PlayerInventory player, ViewComputerContainerData data )
|
||||
{
|
||||
super( Registry.ModContainers.VIEW_COMPUTER.get(), id, player, data );
|
||||
this.width = data.getWidth();
|
||||
this.height = data.getHeight();
|
||||
width = data.getWidth();
|
||||
height = data.getHeight();
|
||||
}
|
||||
|
||||
private static boolean canInteractWith( @Nonnull ServerComputer computer, @Nonnull PlayerEntity player )
|
||||
|
@ -99,6 +99,7 @@ public class AddTurtleTool implements IUndoableAction
|
||||
return String.format( "Removing turtle upgrade %s.", id );
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean validate( ILogger logger )
|
||||
{
|
||||
TrackingLogger trackLog = new TrackingLogger( logger );
|
||||
|
@ -41,7 +41,7 @@ public class RemoveTurtleUpgradeByItem implements IUndoableAction
|
||||
@Override
|
||||
public void undo()
|
||||
{
|
||||
if( this.upgrade != null ) TurtleUpgrades.enable( upgrade );
|
||||
if( upgrade != null ) TurtleUpgrades.enable( upgrade );
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -40,7 +40,7 @@ public class RemoveTurtleUpgradeByName implements IUndoableAction
|
||||
@Override
|
||||
public void undo()
|
||||
{
|
||||
if( this.upgrade != null ) TurtleUpgrades.enable( upgrade );
|
||||
if( upgrade != null ) TurtleUpgrades.enable( upgrade );
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -339,17 +339,17 @@ class RecipeResolver implements IRecipeManagerPlugin
|
||||
UpgradeInfo( ItemStack stack, ITurtleUpgrade turtle )
|
||||
{
|
||||
this.stack = stack;
|
||||
this.ingredient = of( stack );
|
||||
this.upgrade = this.turtle = turtle;
|
||||
this.pocket = null;
|
||||
ingredient = of( stack );
|
||||
upgrade = this.turtle = turtle;
|
||||
pocket = null;
|
||||
}
|
||||
|
||||
UpgradeInfo( ItemStack stack, IPocketUpgrade pocket )
|
||||
{
|
||||
this.stack = stack;
|
||||
this.ingredient = of( stack );
|
||||
this.turtle = null;
|
||||
this.upgrade = this.pocket = pocket;
|
||||
ingredient = of( stack );
|
||||
turtle = null;
|
||||
upgrade = this.pocket = pocket;
|
||||
}
|
||||
|
||||
List<Shaped> getRecipes()
|
||||
|
@ -56,6 +56,9 @@ public final class NetworkHandler
|
||||
registerMainThread( 13, NetworkDirection.PLAY_TO_CLIENT, ComputerTerminalClientMessage::new );
|
||||
registerMainThread( 14, NetworkDirection.PLAY_TO_CLIENT, PlayRecordClientMessage.class, PlayRecordClientMessage::new );
|
||||
registerMainThread( 15, NetworkDirection.PLAY_TO_CLIENT, MonitorClientMessage.class, MonitorClientMessage::new );
|
||||
registerMainThread( 16, NetworkDirection.PLAY_TO_CLIENT, SpeakerPlayClientMessage.class, SpeakerPlayClientMessage::new );
|
||||
registerMainThread( 17, NetworkDirection.PLAY_TO_CLIENT, SpeakerStopClientMessage.class, SpeakerStopClientMessage::new );
|
||||
registerMainThread( 18, NetworkDirection.PLAY_TO_CLIENT, SpeakerMoveClientMessage.class, SpeakerMoveClientMessage::new );
|
||||
}
|
||||
|
||||
public static void sendToPlayer( PlayerEntity player, NetworkMessage packet )
|
||||
|
@ -0,0 +1,58 @@
|
||||
/*
|
||||
* This file is part of ComputerCraft - http://www.computercraft.info
|
||||
* Copyright Daniel Ratcliffe, 2011-2021. Do not distribute without permission.
|
||||
* Send enquiries to dratcliffe@gmail.com
|
||||
*/
|
||||
package dan200.computercraft.shared.network.client;
|
||||
|
||||
import dan200.computercraft.client.SoundManager;
|
||||
import dan200.computercraft.shared.network.NetworkMessage;
|
||||
import net.minecraft.network.PacketBuffer;
|
||||
import net.minecraft.util.math.vector.Vector3d;
|
||||
import net.minecraftforge.api.distmarker.Dist;
|
||||
import net.minecraftforge.api.distmarker.OnlyIn;
|
||||
import net.minecraftforge.fml.network.NetworkEvent;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
* Starts a sound on the client.
|
||||
*
|
||||
* Used by speakers to play sounds.
|
||||
*
|
||||
* @see dan200.computercraft.shared.peripheral.speaker.TileSpeaker
|
||||
*/
|
||||
public class SpeakerMoveClientMessage implements NetworkMessage
|
||||
{
|
||||
private final UUID source;
|
||||
private final Vector3d pos;
|
||||
|
||||
public SpeakerMoveClientMessage( UUID source, Vector3d pos )
|
||||
{
|
||||
this.source = source;
|
||||
this.pos = pos;
|
||||
}
|
||||
|
||||
public SpeakerMoveClientMessage( PacketBuffer buf )
|
||||
{
|
||||
source = buf.readUUID();
|
||||
pos = new Vector3d( buf.readDouble(), buf.readDouble(), buf.readDouble() );
|
||||
}
|
||||
|
||||
@Override
|
||||
public void toBytes( @Nonnull PacketBuffer buf )
|
||||
{
|
||||
buf.writeUUID( source );
|
||||
buf.writeDouble( pos.x() );
|
||||
buf.writeDouble( pos.y() );
|
||||
buf.writeDouble( pos.z() );
|
||||
}
|
||||
|
||||
@Override
|
||||
@OnlyIn( Dist.CLIENT )
|
||||
public void handle( NetworkEvent.Context context )
|
||||
{
|
||||
SoundManager.moveSound( source, pos );
|
||||
}
|
||||
}
|
@ -0,0 +1,74 @@
|
||||
/*
|
||||
* This file is part of ComputerCraft - http://www.computercraft.info
|
||||
* Copyright Daniel Ratcliffe, 2011-2021. Do not distribute without permission.
|
||||
* Send enquiries to dratcliffe@gmail.com
|
||||
*/
|
||||
package dan200.computercraft.shared.network.client;
|
||||
|
||||
import dan200.computercraft.client.SoundManager;
|
||||
import dan200.computercraft.shared.network.NetworkMessage;
|
||||
import net.minecraft.network.PacketBuffer;
|
||||
import net.minecraft.util.ResourceLocation;
|
||||
import net.minecraft.util.SoundEvent;
|
||||
import net.minecraft.util.math.vector.Vector3d;
|
||||
import net.minecraftforge.api.distmarker.Dist;
|
||||
import net.minecraftforge.api.distmarker.OnlyIn;
|
||||
import net.minecraftforge.fml.network.NetworkEvent;
|
||||
import net.minecraftforge.registries.ForgeRegistries;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
* Starts a sound on the client.
|
||||
*
|
||||
* Used by speakers to play sounds.
|
||||
*
|
||||
* @see dan200.computercraft.shared.peripheral.speaker.TileSpeaker
|
||||
*/
|
||||
public class SpeakerPlayClientMessage implements NetworkMessage
|
||||
{
|
||||
private final UUID source;
|
||||
private final Vector3d pos;
|
||||
private final ResourceLocation sound;
|
||||
private final float volume;
|
||||
private final float pitch;
|
||||
|
||||
public SpeakerPlayClientMessage( UUID source, Vector3d pos, ResourceLocation event, float volume, float pitch )
|
||||
{
|
||||
this.source = source;
|
||||
this.pos = pos;
|
||||
sound = event;
|
||||
this.volume = volume;
|
||||
this.pitch = pitch;
|
||||
}
|
||||
|
||||
public SpeakerPlayClientMessage( PacketBuffer buf )
|
||||
{
|
||||
source = buf.readUUID();
|
||||
pos = new Vector3d( buf.readDouble(), buf.readDouble(), buf.readDouble() );
|
||||
sound = buf.readResourceLocation();
|
||||
volume = buf.readFloat();
|
||||
pitch = buf.readFloat();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void toBytes( @Nonnull PacketBuffer buf )
|
||||
{
|
||||
buf.writeUUID( source );
|
||||
buf.writeDouble( pos.x() );
|
||||
buf.writeDouble( pos.y() );
|
||||
buf.writeDouble( pos.z() );
|
||||
buf.writeResourceLocation( sound );
|
||||
buf.writeFloat( volume );
|
||||
buf.writeFloat( pitch );
|
||||
}
|
||||
|
||||
@Override
|
||||
@OnlyIn( Dist.CLIENT )
|
||||
public void handle( NetworkEvent.Context context )
|
||||
{
|
||||
SoundEvent sound = ForgeRegistries.SOUND_EVENTS.getValue( this.sound );
|
||||
if( sound != null ) SoundManager.playSound( source, pos, sound, volume, pitch );
|
||||
}
|
||||
}
|
@ -0,0 +1,51 @@
|
||||
/*
|
||||
* This file is part of ComputerCraft - http://www.computercraft.info
|
||||
* Copyright Daniel Ratcliffe, 2011-2021. Do not distribute without permission.
|
||||
* Send enquiries to dratcliffe@gmail.com
|
||||
*/
|
||||
package dan200.computercraft.shared.network.client;
|
||||
|
||||
import dan200.computercraft.client.SoundManager;
|
||||
import dan200.computercraft.shared.network.NetworkMessage;
|
||||
import net.minecraft.network.PacketBuffer;
|
||||
import net.minecraftforge.api.distmarker.Dist;
|
||||
import net.minecraftforge.api.distmarker.OnlyIn;
|
||||
import net.minecraftforge.fml.network.NetworkEvent;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
* Stops a sound on the client
|
||||
*
|
||||
* Called when a speaker is broken.
|
||||
*
|
||||
* @see dan200.computercraft.shared.peripheral.speaker.TileSpeaker
|
||||
*/
|
||||
public class SpeakerStopClientMessage implements NetworkMessage
|
||||
{
|
||||
private final UUID source;
|
||||
|
||||
public SpeakerStopClientMessage( UUID source )
|
||||
{
|
||||
this.source = source;
|
||||
}
|
||||
|
||||
public SpeakerStopClientMessage( PacketBuffer buf )
|
||||
{
|
||||
source = buf.readUUID();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void toBytes( @Nonnull PacketBuffer buf )
|
||||
{
|
||||
buf.writeUUID( source );
|
||||
}
|
||||
|
||||
@Override
|
||||
@OnlyIn( Dist.CLIENT )
|
||||
public void handle( NetworkEvent.Context context )
|
||||
{
|
||||
SoundManager.stopSound( source );
|
||||
}
|
||||
}
|
@ -54,36 +54,36 @@ public class TerminalState
|
||||
|
||||
if( terminal == null )
|
||||
{
|
||||
this.width = this.height = 0;
|
||||
this.buffer = null;
|
||||
width = height = 0;
|
||||
buffer = null;
|
||||
}
|
||||
else
|
||||
{
|
||||
this.width = terminal.getWidth();
|
||||
this.height = terminal.getHeight();
|
||||
width = terminal.getWidth();
|
||||
height = terminal.getHeight();
|
||||
|
||||
ByteBuf buf = this.buffer = Unpooled.buffer();
|
||||
ByteBuf buf = buffer = Unpooled.buffer();
|
||||
terminal.write( new PacketBuffer( buf ) );
|
||||
}
|
||||
}
|
||||
|
||||
public TerminalState( PacketBuffer buf )
|
||||
{
|
||||
this.colour = buf.readBoolean();
|
||||
this.compress = buf.readBoolean();
|
||||
colour = buf.readBoolean();
|
||||
compress = buf.readBoolean();
|
||||
|
||||
if( buf.readBoolean() )
|
||||
{
|
||||
this.width = buf.readVarInt();
|
||||
this.height = buf.readVarInt();
|
||||
width = buf.readVarInt();
|
||||
height = buf.readVarInt();
|
||||
|
||||
int length = buf.readVarInt();
|
||||
this.buffer = readCompressed( buf, length, compress );
|
||||
buffer = readCompressed( buf, length, compress );
|
||||
}
|
||||
else
|
||||
{
|
||||
this.width = this.height = 0;
|
||||
this.buffer = null;
|
||||
width = height = 0;
|
||||
buffer = null;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -16,14 +16,14 @@ public class ComputerContainerData implements ContainerData
|
||||
|
||||
public ComputerContainerData( ServerComputer computer )
|
||||
{
|
||||
this.id = computer.getInstanceID();
|
||||
this.family = computer.getFamily();
|
||||
id = computer.getInstanceID();
|
||||
family = computer.getFamily();
|
||||
}
|
||||
|
||||
public ComputerContainerData( PacketBuffer buf )
|
||||
{
|
||||
this.id = buf.readInt();
|
||||
this.family = buf.readEnum( ComputerFamily.class );
|
||||
id = buf.readInt();
|
||||
family = buf.readEnum( ComputerFamily.class );
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -24,7 +24,7 @@ final class SaturatedMethod
|
||||
SaturatedMethod( Object target, NamedMethod<PeripheralMethod> method )
|
||||
{
|
||||
this.target = target;
|
||||
this.name = method.getName();
|
||||
name = method.getName();
|
||||
this.method = method.getMethod();
|
||||
}
|
||||
|
||||
|
@ -53,7 +53,7 @@ public class TileCable extends TileGeneric
|
||||
@Override
|
||||
public World getWorld()
|
||||
{
|
||||
return TileCable.this.getLevel();
|
||||
return getLevel();
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
|
@ -87,7 +87,7 @@ public class BlockMonitor extends BlockGeneric
|
||||
{
|
||||
TileMonitor monitor = (TileMonitor) entity;
|
||||
// Defer the block update if we're being placed by another TE. See #691
|
||||
if ( livingEntity == null || livingEntity instanceof FakePlayer )
|
||||
if( livingEntity == null || livingEntity instanceof FakePlayer )
|
||||
{
|
||||
monitor.updateNeighborsDeferred();
|
||||
return;
|
||||
|
@ -10,17 +10,23 @@ import dan200.computercraft.api.lua.ILuaContext;
|
||||
import dan200.computercraft.api.lua.LuaException;
|
||||
import dan200.computercraft.api.lua.LuaFunction;
|
||||
import dan200.computercraft.api.peripheral.IPeripheral;
|
||||
import dan200.computercraft.shared.network.NetworkHandler;
|
||||
import dan200.computercraft.shared.network.client.SpeakerMoveClientMessage;
|
||||
import dan200.computercraft.shared.network.client.SpeakerPlayClientMessage;
|
||||
import net.minecraft.network.play.server.SPlaySoundPacket;
|
||||
import net.minecraft.server.MinecraftServer;
|
||||
import net.minecraft.state.properties.NoteBlockInstrument;
|
||||
import net.minecraft.util.ResourceLocation;
|
||||
import net.minecraft.util.ResourceLocationException;
|
||||
import net.minecraft.util.SoundCategory;
|
||||
import net.minecraft.util.math.BlockPos;
|
||||
import net.minecraft.util.math.MathHelper;
|
||||
import net.minecraft.util.math.vector.Vector3d;
|
||||
import net.minecraft.world.World;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
import java.util.Optional;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
import static dan200.computercraft.api.lua.LuaValues.checkFinite;
|
||||
@ -32,20 +38,44 @@ import static dan200.computercraft.api.lua.LuaValues.checkFinite;
|
||||
*/
|
||||
public abstract class SpeakerPeripheral implements IPeripheral
|
||||
{
|
||||
private static final int MIN_TICKS_BETWEEN_SOUNDS = 1;
|
||||
|
||||
private long clock = 0;
|
||||
private long lastPlayTime = 0;
|
||||
private final AtomicInteger notesThisTick = new AtomicInteger();
|
||||
|
||||
private long lastPositionTime;
|
||||
private Vector3d lastPosition;
|
||||
|
||||
public void update()
|
||||
{
|
||||
clock++;
|
||||
notesThisTick.set( 0 );
|
||||
|
||||
// Push position updates to any speakers which have ever played a note,
|
||||
// have moved by a non-trivial amount and haven't had a position update
|
||||
// in the last second.
|
||||
if( lastPlayTime > 0 && (clock - lastPositionTime) >= 20 )
|
||||
{
|
||||
Vector3d position = getPosition();
|
||||
if( lastPosition == null || lastPosition.distanceToSqr( position ) >= 0.1 )
|
||||
{
|
||||
lastPosition = position;
|
||||
lastPositionTime = clock;
|
||||
NetworkHandler.sendToAllTracking(
|
||||
new SpeakerMoveClientMessage( getSource(), position ),
|
||||
getWorld().getChunkAt( new BlockPos( position ) )
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public abstract World getWorld();
|
||||
|
||||
public abstract Vector3d getPosition();
|
||||
|
||||
protected abstract UUID getSource();
|
||||
|
||||
public boolean madeSound( long ticks )
|
||||
{
|
||||
return clock - lastPlayTime <= ticks;
|
||||
@ -135,26 +165,37 @@ public abstract class SpeakerPeripheral implements IPeripheral
|
||||
|
||||
private synchronized boolean playSound( ILuaContext context, ResourceLocation name, float volume, float pitch, boolean isNote ) throws LuaException
|
||||
{
|
||||
if( clock - lastPlayTime < TileSpeaker.MIN_TICKS_BETWEEN_SOUNDS &&
|
||||
(!isNote || clock - lastPlayTime != 0 || notesThisTick.get() >= ComputerCraft.maxNotesPerTick) )
|
||||
if( clock - lastPlayTime < MIN_TICKS_BETWEEN_SOUNDS )
|
||||
{
|
||||
// Rate limiting occurs when we've already played a sound within the last tick, or we've
|
||||
// played more notes than allowable within the current tick.
|
||||
return false;
|
||||
// Rate limiting occurs when we've already played a sound within the last tick.
|
||||
if( !isNote ) return false;
|
||||
// Or we've played more notes than allowable within the current tick.
|
||||
if( clock - lastPlayTime != 0 || notesThisTick.get() >= ComputerCraft.maxNotesPerTick ) return false;
|
||||
}
|
||||
|
||||
World world = getWorld();
|
||||
Vector3d pos = getPosition();
|
||||
|
||||
float range = MathHelper.clamp( volume, 1.0f, 3.0f ) * 16;
|
||||
|
||||
context.issueMainThreadTask( () -> {
|
||||
MinecraftServer server = world.getServer();
|
||||
if( server == null ) return null;
|
||||
|
||||
float adjVolume = Math.min( volume, 3.0f );
|
||||
server.getPlayerList().broadcast(
|
||||
null, pos.x, pos.y, pos.z, adjVolume > 1.0f ? 16 * adjVolume : 16.0, world.dimension(),
|
||||
new SPlaySoundPacket( name, SoundCategory.RECORDS, pos, adjVolume, pitch )
|
||||
);
|
||||
if( isNote )
|
||||
{
|
||||
server.getPlayerList().broadcast(
|
||||
null, pos.x, pos.y, pos.z, range, world.dimension(),
|
||||
new SPlaySoundPacket( name, SoundCategory.RECORDS, pos, range, pitch )
|
||||
);
|
||||
}
|
||||
else
|
||||
{
|
||||
NetworkHandler.sendToAllAround(
|
||||
new SpeakerPlayClientMessage( getSource(), pos, name, range, pitch ),
|
||||
world, pos, range
|
||||
);
|
||||
}
|
||||
return null;
|
||||
} );
|
||||
|
||||
|
@ -7,6 +7,8 @@ package dan200.computercraft.shared.peripheral.speaker;
|
||||
|
||||
import dan200.computercraft.api.peripheral.IPeripheral;
|
||||
import dan200.computercraft.shared.common.TileGeneric;
|
||||
import dan200.computercraft.shared.network.NetworkHandler;
|
||||
import dan200.computercraft.shared.network.client.SpeakerStopClientMessage;
|
||||
import dan200.computercraft.shared.util.CapabilityUtil;
|
||||
import net.minecraft.tileentity.ITickableTileEntity;
|
||||
import net.minecraft.tileentity.TileEntityType;
|
||||
@ -19,15 +21,15 @@ import net.minecraftforge.common.util.LazyOptional;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
import javax.annotation.Nullable;
|
||||
import java.util.UUID;
|
||||
|
||||
import static dan200.computercraft.shared.Capabilities.CAPABILITY_PERIPHERAL;
|
||||
|
||||
public class TileSpeaker extends TileGeneric implements ITickableTileEntity
|
||||
{
|
||||
public static final int MIN_TICKS_BETWEEN_SOUNDS = 1;
|
||||
|
||||
private final SpeakerPeripheral peripheral;
|
||||
private LazyOptional<IPeripheral> peripheralCap;
|
||||
private final UUID source = UUID.randomUUID();
|
||||
|
||||
public TileSpeaker( TileEntityType<TileSpeaker> type )
|
||||
{
|
||||
@ -41,6 +43,13 @@ public class TileSpeaker extends TileGeneric implements ITickableTileEntity
|
||||
peripheral.update();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setRemoved()
|
||||
{
|
||||
super.setRemoved();
|
||||
NetworkHandler.sendToAllPlayers( new SpeakerStopClientMessage( source ) );
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
@Override
|
||||
public <T> LazyOptional<T> getCapability( @Nonnull Capability<T> cap, @Nullable Direction side )
|
||||
@ -83,6 +92,12 @@ public class TileSpeaker extends TileGeneric implements ITickableTileEntity
|
||||
return new Vector3d( pos.getX(), pos.getY(), pos.getZ() );
|
||||
}
|
||||
|
||||
@Override
|
||||
protected UUID getSource()
|
||||
{
|
||||
return speaker.source;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals( @Nullable IPeripheral other )
|
||||
{
|
||||
|
@ -0,0 +1,33 @@
|
||||
/*
|
||||
* This file is part of ComputerCraft - http://www.computercraft.info
|
||||
* Copyright Daniel Ratcliffe, 2011-2021. Do not distribute without permission.
|
||||
* Send enquiries to dratcliffe@gmail.com
|
||||
*/
|
||||
package dan200.computercraft.shared.peripheral.speaker;
|
||||
|
||||
import dan200.computercraft.api.peripheral.IComputerAccess;
|
||||
import dan200.computercraft.shared.network.NetworkHandler;
|
||||
import dan200.computercraft.shared.network.client.SpeakerStopClientMessage;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
* A speaker peripheral which is used on an upgrade, and so is only attached to one computer.
|
||||
*/
|
||||
public abstract class UpgradeSpeakerPeripheral extends SpeakerPeripheral
|
||||
{
|
||||
private final UUID source = UUID.randomUUID();
|
||||
|
||||
@Override
|
||||
protected final UUID getSource()
|
||||
{
|
||||
return source;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void detach( @Nonnull IComputerAccess computer )
|
||||
{
|
||||
NetworkHandler.sendToAllPlayers( new SpeakerStopClientMessage( source ) );
|
||||
}
|
||||
}
|
@ -46,7 +46,7 @@ public final class ContainerPocketComputer extends ContainerComputerBase
|
||||
public Factory( ServerComputer computer, ItemStack stack, ItemPocketComputer item, Hand hand )
|
||||
{
|
||||
this.computer = computer;
|
||||
this.name = stack.getHoverName();
|
||||
name = stack.getHoverName();
|
||||
this.item = item;
|
||||
this.hand = hand;
|
||||
}
|
||||
|
@ -6,11 +6,11 @@
|
||||
package dan200.computercraft.shared.pocket.peripherals;
|
||||
|
||||
import dan200.computercraft.api.peripheral.IPeripheral;
|
||||
import dan200.computercraft.shared.peripheral.speaker.SpeakerPeripheral;
|
||||
import dan200.computercraft.shared.peripheral.speaker.UpgradeSpeakerPeripheral;
|
||||
import net.minecraft.util.math.vector.Vector3d;
|
||||
import net.minecraft.world.World;
|
||||
|
||||
public class PocketSpeakerPeripheral extends SpeakerPeripheral
|
||||
public class PocketSpeakerPeripheral extends UpgradeSpeakerPeripheral
|
||||
{
|
||||
private World world = null;
|
||||
private Vector3d position = Vector3d.ZERO;
|
||||
|
@ -715,7 +715,6 @@ public class TurtleAPI implements ILuaAPI
|
||||
* more information about the item at the cost of taking longer to run.
|
||||
* @return The command result.
|
||||
* @throws LuaException If the slot is out of range.
|
||||
* @see InventoryMethods#getItemDetail Describes the information returned by a detailed query.
|
||||
* @cc.treturn nil|table Information about the given slot, or {@code nil} if it is empty.
|
||||
* @cc.usage Print the current slot, assuming it contains 13 dirt.
|
||||
*
|
||||
@ -726,6 +725,7 @@ public class TurtleAPI implements ILuaAPI
|
||||
* -- count = 13,
|
||||
* -- }
|
||||
* }</pre>
|
||||
* @see InventoryMethods#getItemDetail Describes the information returned by a detailed query.
|
||||
*/
|
||||
@LuaFunction
|
||||
public final MethodResult getItemDetail( ILuaContext context, Optional<Integer> slot, Optional<Boolean> detailed ) throws LuaException
|
||||
|
@ -503,20 +503,15 @@ public class TurtleBrain implements ITurtleAccess
|
||||
setFuelLevel( getFuelLevel() + addition );
|
||||
}
|
||||
|
||||
private int issueCommand( ITurtleCommand command )
|
||||
{
|
||||
commandQueue.offer( new TurtleCommandQueueEntry( ++commandsIssued, command ) );
|
||||
return commandsIssued;
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
@Override
|
||||
public MethodResult executeCommand( @Nonnull ITurtleCommand command )
|
||||
{
|
||||
if( getWorld().isClientSide ) throw new UnsupportedOperationException( "Cannot run commands on the client" );
|
||||
if( commandQueue.size() > 16 ) return MethodResult.of( false, "Too many ongoing turtle commands" );
|
||||
|
||||
// Issue command
|
||||
int commandID = issueCommand( command );
|
||||
commandQueue.offer( new TurtleCommandQueueEntry( ++commandsIssued, command ) );
|
||||
int commandID = commandsIssued;
|
||||
return new CommandCallback( commandID ).pull;
|
||||
}
|
||||
|
||||
|
@ -5,6 +5,7 @@
|
||||
*/
|
||||
package dan200.computercraft.shared.turtle.inventory;
|
||||
|
||||
import dan200.computercraft.client.gui.widgets.ComputerSidebar;
|
||||
import dan200.computercraft.shared.Registry;
|
||||
import dan200.computercraft.shared.computer.core.ComputerFamily;
|
||||
import dan200.computercraft.shared.computer.core.IComputer;
|
||||
@ -27,8 +28,10 @@ import java.util.function.Predicate;
|
||||
|
||||
public class ContainerTurtle extends ContainerComputerBase
|
||||
{
|
||||
public static final int BORDER = 8;
|
||||
public static final int PLAYER_START_Y = 134;
|
||||
public static final int TURTLE_START_X = 175;
|
||||
public static final int TURTLE_START_X = ComputerSidebar.WIDTH + 175;
|
||||
public static final int PLAYER_START_X = ComputerSidebar.WIDTH + BORDER;
|
||||
|
||||
private final IIntArray properties;
|
||||
|
||||
@ -56,14 +59,14 @@ public class ContainerTurtle extends ContainerComputerBase
|
||||
{
|
||||
for( int x = 0; x < 9; x++ )
|
||||
{
|
||||
addSlot( new Slot( playerInventory, x + y * 9 + 9, 8 + x * 18, PLAYER_START_Y + 1 + y * 18 ) );
|
||||
addSlot( new Slot( playerInventory, x + y * 9 + 9, PLAYER_START_X + x * 18, PLAYER_START_Y + 1 + y * 18 ) );
|
||||
}
|
||||
}
|
||||
|
||||
// Player hotbar
|
||||
for( int x = 0; x < 9; x++ )
|
||||
{
|
||||
addSlot( new Slot( playerInventory, x, 8 + x * 18, PLAYER_START_Y + 3 * 18 + 5 ) );
|
||||
addSlot( new Slot( playerInventory, x, PLAYER_START_X + x * 18, PLAYER_START_Y + 3 * 18 + 5 ) );
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -12,7 +12,7 @@ import dan200.computercraft.api.turtle.ITurtleAccess;
|
||||
import dan200.computercraft.api.turtle.TurtleSide;
|
||||
import dan200.computercraft.api.turtle.TurtleUpgradeType;
|
||||
import dan200.computercraft.shared.Registry;
|
||||
import dan200.computercraft.shared.peripheral.speaker.SpeakerPeripheral;
|
||||
import dan200.computercraft.shared.peripheral.speaker.UpgradeSpeakerPeripheral;
|
||||
import net.minecraft.client.renderer.model.ModelResourceLocation;
|
||||
import net.minecraft.util.ResourceLocation;
|
||||
import net.minecraft.util.math.BlockPos;
|
||||
@ -28,7 +28,7 @@ public class TurtleSpeaker extends AbstractTurtleUpgrade
|
||||
private static final ModelResourceLocation leftModel = new ModelResourceLocation( "computercraft:turtle_speaker_upgrade_left", "inventory" );
|
||||
private static final ModelResourceLocation rightModel = new ModelResourceLocation( "computercraft:turtle_speaker_upgrade_right", "inventory" );
|
||||
|
||||
private static class Peripheral extends SpeakerPeripheral
|
||||
private static class Peripheral extends UpgradeSpeakerPeripheral
|
||||
{
|
||||
ITurtleAccess turtle;
|
||||
|
||||
|
@ -65,7 +65,7 @@ public class TurtleTool extends AbstractTurtleUpgrade
|
||||
public TurtleTool( ResourceLocation id, ItemStack craftItem, ItemStack toolItem )
|
||||
{
|
||||
super( id, TurtleUpgradeType.TOOL, craftItem );
|
||||
this.item = toolItem;
|
||||
item = toolItem;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -318,7 +318,7 @@ public class FakeNetHandler extends ServerPlayNetHandler
|
||||
@Override
|
||||
public void disconnect( @Nonnull ITextComponent message )
|
||||
{
|
||||
this.closeReason = message;
|
||||
closeReason = message;
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
|
@ -110,5 +110,11 @@
|
||||
"tracking_field.computercraft.coroutines_dead.name": "Coroutines disposed",
|
||||
"gui.computercraft.tooltip.copy": "Copy to clipboard",
|
||||
"gui.computercraft.tooltip.computer_id": "Computer ID: %s",
|
||||
"gui.computercraft.tooltip.disk_id": "Disk ID: %s"
|
||||
"gui.computercraft.tooltip.disk_id": "Disk ID: %s",
|
||||
"gui.computercraft.tooltip.turn_on": "Turn this computer on",
|
||||
"gui.computercraft.tooltip.turn_on.key": "Hold Ctrl+R",
|
||||
"gui.computercraft.tooltip.turn_off": "Turn this computer off",
|
||||
"gui.computercraft.tooltip.turn_off.key": "Hold Ctrl+S",
|
||||
"gui.computercraft.tooltip.terminate": "Stop the currently running code",
|
||||
"gui.computercraft.tooltip.terminate.key": "Hold Ctrl+T"
|
||||
}
|
||||
|
BIN
src/main/resources/assets/computercraft/textures/gui/buttons.png
Normal file
After Width: | Height: | Size: 303 B |
Before Width: | Height: | Size: 352 B After Width: | Height: | Size: 405 B |
Before Width: | Height: | Size: 293 B After Width: | Height: | Size: 345 B |
Before Width: | Height: | Size: 347 B After Width: | Height: | Size: 399 B |
@ -27,21 +27,24 @@ function setPath(_sPath)
|
||||
sPath = _sPath
|
||||
end
|
||||
|
||||
local extensions = { "", ".md", ".txt" }
|
||||
|
||||
--- Returns the location of the help file for the given topic.
|
||||
--
|
||||
-- @tparam string topic The topic to find
|
||||
-- @treturn string|nil The path to the given topic's help file, or `nil` if it
|
||||
-- cannot be found.
|
||||
-- @usage help.lookup("disk")
|
||||
function lookup(_sTopic)
|
||||
expect(1, _sTopic, "string")
|
||||
function lookup(topic)
|
||||
expect(1, topic, "string")
|
||||
-- Look on the path variable
|
||||
for sPath in string.gmatch(sPath, "[^:]+") do
|
||||
sPath = fs.combine(sPath, _sTopic)
|
||||
if fs.exists(sPath) and not fs.isDir(sPath) then
|
||||
return sPath
|
||||
elseif fs.exists(sPath .. ".txt") and not fs.isDir(sPath .. ".txt") then
|
||||
return sPath .. ".txt"
|
||||
for path in string.gmatch(sPath, "[^:]+") do
|
||||
path = fs.combine(path, topic)
|
||||
for _, extension in ipairs(extensions) do
|
||||
local file = path .. extension
|
||||
if fs.exists(file) and not fs.isDir(file) then
|
||||
return file
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@ -66,8 +69,11 @@ function topics()
|
||||
for _, sFile in pairs(tList) do
|
||||
if string.sub(sFile, 1, 1) ~= "." then
|
||||
if not fs.isDir(fs.combine(sPath, sFile)) then
|
||||
if #sFile > 4 and sFile:sub(-4) == ".txt" then
|
||||
sFile = sFile:sub(1, -5)
|
||||
for i = 2, #extensions do
|
||||
local extension = extensions[i]
|
||||
if #sFile > #extension and sFile:sub(-#extension) == extension then
|
||||
sFile = sFile:sub(1, -#extension - 1)
|
||||
end
|
||||
end
|
||||
tItems[sFile] = true
|
||||
end
|
||||
|
@ -1,5 +1,5 @@
|
||||
craft is a program for Crafty Turtles. Craft will craft a stack of items using the current inventory.
|
||||
|
||||
ex:
|
||||
"craft" will craft as many items as possible
|
||||
"craft all" will craft as many items as possible
|
||||
"craft 5" will craft at most 5 times
|
||||
|
@ -29,8 +29,8 @@ local completion = require "cc.completion"
|
||||
|
||||
--- Complete the name of a file relative to the current working directory.
|
||||
--
|
||||
-- @tparam table shell The shell we're completing in
|
||||
-- @tparam { string... } choices The list of choices to complete from.
|
||||
-- @tparam table shell The shell we're completing in.
|
||||
-- @tparam string text Current text to complete.
|
||||
-- @treturn { string... } A list of suffixes of matching files.
|
||||
local function file(shell, text)
|
||||
return fs.complete(text, shell.dir(), true, false)
|
||||
@ -38,8 +38,8 @@ end
|
||||
|
||||
--- Complete the name of a directory relative to the current working directory.
|
||||
--
|
||||
-- @tparam table shell The shell we're completing in
|
||||
-- @tparam { string... } choices The list of choices to complete from.
|
||||
-- @tparam table shell The shell we're completing in.
|
||||
-- @tparam string text Current text to complete.
|
||||
-- @treturn { string... } A list of suffixes of matching directories.
|
||||
local function dir(shell, text)
|
||||
return fs.complete(text, shell.dir(), false, true)
|
||||
@ -48,8 +48,8 @@ end
|
||||
--- Complete the name of a file or directory relative to the current working
|
||||
-- directory.
|
||||
--
|
||||
-- @tparam table shell The shell we're completing in
|
||||
-- @tparam { string... } choices The list of choices to complete from.
|
||||
-- @tparam table shell The shell we're completing in.
|
||||
-- @tparam string text Current text to complete.
|
||||
-- @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.
|
||||
@ -74,14 +74,46 @@ end
|
||||
|
||||
--- Complete the name of a program.
|
||||
--
|
||||
-- @tparam table shell The shell we're completing in
|
||||
-- @tparam { string... } choices The list of choices to complete from.
|
||||
-- @tparam table shell The shell we're completing in.
|
||||
-- @tparam string text Current text to complete.
|
||||
-- @treturn { string... } A list of suffixes of matching programs.
|
||||
-- @see shell.completeProgram
|
||||
local function program(shell, text)
|
||||
return shell.completeProgram(text)
|
||||
end
|
||||
|
||||
--- Complete arguments of a program.
|
||||
--
|
||||
-- @tparam table shell The shell we're completing in.
|
||||
-- @tparam string text Current text to complete.
|
||||
-- @tparam { string... } previous The shell arguments before this one.
|
||||
-- @tparam number starting Which argument index this program and args start at.
|
||||
-- @treturn { string... } A list of suffixes of matching programs or arguments.
|
||||
local function programWithArgs(shell, text, previous, starting)
|
||||
if #previous + 1 == starting then
|
||||
local tCompletionInfo = shell.getCompletionInfo()
|
||||
if text:sub(-1) ~= "/" and tCompletionInfo[shell.resolveProgram(text)] then
|
||||
return { " " }
|
||||
else
|
||||
local results = shell.completeProgram(text)
|
||||
for n = 1, #results do
|
||||
local sResult = results[n]
|
||||
if sResult:sub(-1) ~= "/" and tCompletionInfo[shell.resolveProgram(text .. sResult)] then
|
||||
results[n] = sResult .. " "
|
||||
end
|
||||
end
|
||||
return results
|
||||
end
|
||||
else
|
||||
local program = previous[starting]
|
||||
local resolved = shell.resolveProgram(program)
|
||||
if not resolved then return end
|
||||
local tCompletion = shell.getCompletionInfo()[resolved]
|
||||
if not tCompletion then return end
|
||||
return tCompletion.fnComplete(shell, #previous - starting + 1, text, { program, table.unpack(previous, starting + 1, #previous) })
|
||||
end
|
||||
end
|
||||
|
||||
--[[- A helper function for building shell completion arguments.
|
||||
|
||||
This accepts a series of single-argument completion functions, and combines
|
||||
@ -144,6 +176,7 @@ return {
|
||||
dir = dir,
|
||||
dirOrFile = dirOrFile,
|
||||
program = program,
|
||||
programWithArgs = programWithArgs,
|
||||
|
||||
-- Re-export various other functions
|
||||
help = wrap(help.completeTopic), --- Wraps @{help.completeTopic} as a @{build} compatible function.
|
||||
|
@ -14,16 +14,127 @@ if sTopic == "index" then
|
||||
end
|
||||
|
||||
local strings = require "cc.strings"
|
||||
local function word_wrap(text, width)
|
||||
local lines = strings.wrap(text, width)
|
||||
|
||||
local function min_of(a, b, default)
|
||||
if not a and not b then return default end
|
||||
if not a then return b end
|
||||
if not b then return a end
|
||||
return math.min(a, b)
|
||||
end
|
||||
|
||||
--[[- Parse a markdown string, extracting headings and highlighting some basic
|
||||
constructs.
|
||||
|
||||
The implementation of this is horrible. SquidDev shouldn't be allowed to write
|
||||
parsers, especially ones they think might be "performance critical".
|
||||
]]
|
||||
local function parse_markdown(text)
|
||||
local len = #text
|
||||
local oob = len + 1
|
||||
|
||||
-- Some patterns to match headers and bullets on the start of lines.
|
||||
-- The `%f[^\n\0]` is some wonderful logic to match the start of a line /or/
|
||||
-- the start of the document.
|
||||
local heading = "%f[^\n\0](#+ +)([^\n]*)"
|
||||
local bullet = "%f[^\n\0]( *)[.*]( +)"
|
||||
local code = "`([^`]+)`"
|
||||
|
||||
local new_text, fg, bg = "", "", ""
|
||||
local function append(txt, fore, back)
|
||||
new_text = new_text .. txt
|
||||
fg = fg .. (fore or "0"):rep(#txt)
|
||||
bg = bg .. (back or "f"):rep(#txt)
|
||||
end
|
||||
|
||||
local next_header = text:find(heading)
|
||||
local next_bullet = text:find(bullet)
|
||||
local next_block = min_of(next_header, next_bullet, oob)
|
||||
|
||||
local next_code, next_code_end = text:find(code)
|
||||
|
||||
local sections = {}
|
||||
|
||||
local start = 1
|
||||
while start <= len do
|
||||
if start == next_block then
|
||||
if start == next_header then
|
||||
local _, fin, head, content = text:find(heading, start)
|
||||
sections[#new_text + 1] = content
|
||||
append(head .. content, "4", "f")
|
||||
start = fin + 1
|
||||
|
||||
next_header = text:find(heading, start)
|
||||
else
|
||||
local _, fin, space, content = text:find(bullet, start)
|
||||
append(space .. "\7" .. content)
|
||||
start = fin + 1
|
||||
|
||||
next_bullet = text:find(bullet, start)
|
||||
end
|
||||
|
||||
next_block = min_of(next_header, next_bullet, oob)
|
||||
elseif next_code and next_code_end < next_block then
|
||||
-- Basic inline code blocks
|
||||
if start < next_code then append(text:sub(start, next_code - 1)) end
|
||||
local content = text:match(code, next_code)
|
||||
append(content, "0", "7")
|
||||
|
||||
start = next_code_end + 1
|
||||
next_code, next_code_end = text:find(code, start)
|
||||
else
|
||||
-- Normal text
|
||||
append(text:sub(start, next_block - 1))
|
||||
start = next_block
|
||||
|
||||
-- Rescan for a new code block
|
||||
if next_code then next_code, next_code_end = text:find(code, start) end
|
||||
end
|
||||
end
|
||||
|
||||
return new_text, fg, bg, sections
|
||||
end
|
||||
|
||||
local function word_wrap_basic(text, width)
|
||||
local lines, fg, bg = strings.wrap(text, width), {}, {}
|
||||
local fg_line, bg_line = ("0"):rep(width), ("f"):rep(width)
|
||||
|
||||
-- Normalise the strings suitable for use with blit. We could skip this and
|
||||
-- just use term.write, but saves us a clearLine call.
|
||||
for k, line in pairs(lines) do
|
||||
lines[k] = strings.ensure_width(line, width)
|
||||
fg[k] = fg_line
|
||||
bg[k] = bg_line
|
||||
end
|
||||
|
||||
return lines
|
||||
return lines, fg, bg, {}
|
||||
end
|
||||
|
||||
local function word_wrap_markdown(text, width)
|
||||
-- Add in styling for Markdown-formatted text.
|
||||
local text, fg, bg, sections = parse_markdown(text)
|
||||
|
||||
local lines = strings.wrap(text, width)
|
||||
local fglines, bglines, section_list, section_n = {}, {}, {}, 1
|
||||
|
||||
-- Normalise the strings suitable for use with blit. We could skip this and
|
||||
-- just use term.write, but saves us a clearLine call.
|
||||
local start = 1
|
||||
for k, line in pairs(lines) do
|
||||
-- I hate this with a burning passion, but it works!
|
||||
local pos = text:find(line, start, true)
|
||||
lines[k], fglines[k], bglines[k] =
|
||||
strings.ensure_width(line, width),
|
||||
strings.ensure_width(fg:sub(pos, pos + #line), width),
|
||||
strings.ensure_width(bg:sub(pos, pos + #line), width)
|
||||
|
||||
if sections[pos] then
|
||||
section_list[section_n], section_n = { content = sections[pos], offset = k - 1 }, section_n + 1
|
||||
end
|
||||
|
||||
start = pos + 1
|
||||
end
|
||||
|
||||
return lines, fglines, bglines, section_list
|
||||
end
|
||||
|
||||
local sFile = help.lookup(sTopic)
|
||||
@ -33,31 +144,40 @@ if not file then
|
||||
return
|
||||
end
|
||||
|
||||
local contents = file:read("*a"):gsub("(\n *)[-*]( +)", "%1\7%2")
|
||||
local contents = file:read("*a")
|
||||
file:close()
|
||||
|
||||
local word_wrap = sFile:sub(-3) == ".md" and word_wrap_markdown or word_wrap_basic
|
||||
local width, height = term.getSize()
|
||||
local lines = word_wrap(contents, width)
|
||||
local lines, fg, bg, sections = word_wrap(contents, width)
|
||||
local print_height = #lines
|
||||
|
||||
-- If we fit within the screen, just display without pagination.
|
||||
if print_height <= height then
|
||||
print(contents)
|
||||
local _, y = term.getCursorPos()
|
||||
for i = 1, print_height do
|
||||
if y + i - 1 > height then
|
||||
term.scroll(1)
|
||||
term.setCursorPos(1, height)
|
||||
else
|
||||
term.setCursorPos(1, y + i - 1)
|
||||
end
|
||||
|
||||
term.blit(lines[i], fg[i], bg[i])
|
||||
end
|
||||
return
|
||||
end
|
||||
|
||||
local current_section = nil
|
||||
local offset = 0
|
||||
|
||||
local function draw()
|
||||
local fg, bg = ("0"):rep(width), ("f"):rep(width)
|
||||
for y = 1, height - 1 do
|
||||
term.setCursorPos(1, y)
|
||||
if y + offset > print_height then
|
||||
-- Should only happen if we resize the terminal to a larger one
|
||||
-- than actually needed for the current text.
|
||||
term.clearLine()
|
||||
else
|
||||
term.blit(lines[y + offset], fg, bg)
|
||||
--- Find the currently visible seciton, or nil if this document has no sections.
|
||||
--
|
||||
-- This could potentially be a binary search, but right now it's not worth it.
|
||||
local function find_section()
|
||||
for i = #sections, 1, -1 do
|
||||
if sections[i].offset <= offset then
|
||||
return i
|
||||
end
|
||||
end
|
||||
end
|
||||
@ -68,7 +188,10 @@ local function draw_menu()
|
||||
term.clearLine()
|
||||
|
||||
local tag = "Help: " .. sTopic
|
||||
term.write("Help: " .. sTopic)
|
||||
if current_section then
|
||||
tag = tag .. (" (%s)"):format(sections[current_section].content)
|
||||
end
|
||||
term.write(tag)
|
||||
|
||||
if width >= #tag + 16 then
|
||||
term.setCursorPos(width - 14, height)
|
||||
@ -76,11 +199,31 @@ local function draw_menu()
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
local function draw()
|
||||
for y = 1, height - 1 do
|
||||
term.setCursorPos(1, y)
|
||||
if y + offset > print_height then
|
||||
-- Should only happen if we resize the terminal to a larger one
|
||||
-- than actually needed for the current text.
|
||||
term.clearLine()
|
||||
else
|
||||
term.blit(lines[y + offset], fg[y + offset], bg[y + offset])
|
||||
end
|
||||
end
|
||||
|
||||
local new_section = find_section()
|
||||
if new_section ~= current_section then
|
||||
current_section = new_section
|
||||
draw_menu()
|
||||
end
|
||||
end
|
||||
|
||||
draw()
|
||||
draw_menu()
|
||||
|
||||
while true do
|
||||
local event, param = os.pullEvent()
|
||||
local event, param = os.pullEventRaw()
|
||||
if event == "key" then
|
||||
if param == keys.up and offset > 0 then
|
||||
offset = offset - 1
|
||||
@ -97,6 +240,12 @@ while true do
|
||||
elseif param == keys.home then
|
||||
offset = 0
|
||||
draw()
|
||||
elseif param == keys.left and current_section and current_section > 1 then
|
||||
offset = sections[current_section - 1].offset
|
||||
draw()
|
||||
elseif param == keys.right and current_section and current_section < #sections then
|
||||
offset = sections[current_section + 1].offset
|
||||
draw()
|
||||
elseif param == keys["end"] then
|
||||
offset = print_height - height
|
||||
draw()
|
||||
@ -124,6 +273,8 @@ while true do
|
||||
offset = math.max(math.min(offset, print_height - height), 0)
|
||||
draw()
|
||||
draw_menu()
|
||||
elseif event == "terminate" then
|
||||
break
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -51,7 +51,7 @@ local function get(sUrl)
|
||||
|
||||
local sResponse = response.readAll()
|
||||
response.close()
|
||||
return sResponse
|
||||
return sResponse or ""
|
||||
end
|
||||
|
||||
if run then
|
||||
@ -79,7 +79,12 @@ else
|
||||
local res = get(url)
|
||||
if not res then return end
|
||||
|
||||
local file = fs.open(sPath, "wb")
|
||||
local file, err = fs.open(sPath, "wb")
|
||||
if not file then
|
||||
printError("Cannot save file: " .. err)
|
||||
return
|
||||
end
|
||||
|
||||
file.write(res)
|
||||
file.close()
|
||||
|
||||
|
@ -7,7 +7,7 @@ local function printUsage()
|
||||
end
|
||||
|
||||
local tArgs = { ... }
|
||||
if #tArgs < 2 then
|
||||
if #tArgs < 2 or tArgs[1] == "scale" and #tArgs < 3 then
|
||||
printUsage()
|
||||
return
|
||||
end
|
||||
@ -21,7 +21,7 @@ if tArgs[1] == "scale" then
|
||||
|
||||
local nRes = tonumber(tArgs[3])
|
||||
if nRes == nil or nRes < 0.5 or nRes > 5 then
|
||||
print("Invalid scale: " .. nRes)
|
||||
print("Invalid scale: " .. tArgs[3])
|
||||
return
|
||||
end
|
||||
|
||||
|
@ -9,20 +9,19 @@ if not turtle.craft then
|
||||
end
|
||||
|
||||
local tArgs = { ... }
|
||||
local nLimit = nil
|
||||
if #tArgs < 1 then
|
||||
local nLimit = tonumber(tArgs[1])
|
||||
|
||||
if not nLimit and tArgs[1] ~= "all" then
|
||||
local programName = arg[0] or fs.getName(shell.getRunningProgram())
|
||||
print("Usage: " .. programName .. " [number]")
|
||||
print("Usage: " .. programName .. " all|<number>")
|
||||
return
|
||||
else
|
||||
nLimit = tonumber(tArgs[1])
|
||||
end
|
||||
|
||||
local nCrafted = 0
|
||||
local nOldCount = turtle.getItemCount(turtle.getSelectedSlot())
|
||||
if turtle.craft(nLimit) then
|
||||
local nNewCount = turtle.getItemCount(turtle.getSelectedSlot())
|
||||
if nOldCount <= nLimit then
|
||||
if not nLimit or nOldCount <= nLimit then
|
||||
nCrafted = nNewCount
|
||||
else
|
||||
nCrafted = nOldCount - nNewCount
|
||||
|
@ -81,9 +81,17 @@ shell.setCompletionFunction("rom/programs/monitor.lua", completion.build(
|
||||
if previous[2] == "scale" then
|
||||
return completion.peripheral(shell, text, previous, true)
|
||||
else
|
||||
return completion.program(shell, text, previous)
|
||||
return completion.programWithArgs(shell, text, previous, 3)
|
||||
end
|
||||
end
|
||||
end,
|
||||
{
|
||||
function(shell, text, previous)
|
||||
if previous[2] ~= "scale" then
|
||||
return completion.programWithArgs(shell, text, previous, 3)
|
||||
end
|
||||
end,
|
||||
many = true,
|
||||
}
|
||||
))
|
||||
|
||||
shell.setCompletionFunction("rom/programs/move.lua", completion.build(
|
||||
@ -98,11 +106,11 @@ 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/shell.lua", completion.build({ completion.programWithArgs, 2, many = true }))
|
||||
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/advanced/bg.lua", completion.build({ completion.programWithArgs, 2, many = true }))
|
||||
shell.setCompletionFunction("rom/programs/advanced/fg.lua", completion.build({ completion.programWithArgs, 2, many = true }))
|
||||
shell.setCompletionFunction("rom/programs/fun/dj.lua", completion.build(
|
||||
{ completion.choice, { "play", "play ", "stop " } },
|
||||
completion.peripheral
|
||||
|
@ -32,7 +32,7 @@ public class AddressRuleTest
|
||||
@ParameterizedTest
|
||||
@ValueSource( strings = {
|
||||
"0.0.0.0", "[::]",
|
||||
"localhost", "lvh.me", "127.0.0.1", "[::1]",
|
||||
"localhost", "127.0.0.1.nip.io", "127.0.0.1", "[::1]",
|
||||
"172.17.0.1", "192.168.1.114", "[0:0:0:0:0:ffff:c0a8:172]", "10.0.0.1"
|
||||
} )
|
||||
public void blocksLocalDomains( String domain )
|
||||
|
@ -18,5 +18,10 @@ describe("The help library", function()
|
||||
help.completeTopic("")
|
||||
expect.error(help.completeTopic, nil):eq("bad argument #1 (expected string, got nil)")
|
||||
end)
|
||||
|
||||
it("completes topics without extensions", function()
|
||||
expect(help.completeTopic("changel")):same { "og" }
|
||||
expect(help.completeTopic("turt")):same { "le" }
|
||||
end)
|
||||
end)
|
||||
end)
|
||||
|
@ -17,6 +17,30 @@ describe("cc.shell.completion", function()
|
||||
end)
|
||||
end)
|
||||
|
||||
describe("program", function()
|
||||
it("completes programs", function()
|
||||
expect(c.program(shell, "rom/")):same {
|
||||
"apis/", "autorun/", "help/", "modules/", "motd.txt", "programs/", "startup.lua",
|
||||
}
|
||||
end)
|
||||
end)
|
||||
|
||||
describe("programWithArgs", function()
|
||||
it("completes program name", function()
|
||||
shell.setCompletionFunction("rom/motd.txt", function() end)
|
||||
expect(c.programWithArgs(shell, "rom/", { "rom/programs/shell.lua" }, 2)):same {
|
||||
"apis/", "autorun/", "help/", "modules/", "motd.txt ", "programs/", "startup.lua",
|
||||
}
|
||||
end)
|
||||
|
||||
it("completes program arguments", function()
|
||||
expect(c.programWithArgs(shell, "", { "rom/programs/shell.lua", "pastebin" }, 2)):same {
|
||||
"put ", "get ", "run ",
|
||||
}
|
||||
end)
|
||||
|
||||
end)
|
||||
|
||||
describe("build", function()
|
||||
it("completes multiple arguments", function()
|
||||
local spec = c.build(
|
||||
|
@ -1,7 +1,8 @@
|
||||
local capture = require "test_helpers".capture_program
|
||||
|
||||
describe("The wget program", function()
|
||||
local function setup_request()
|
||||
local default_contents = [[print("Hello", ...)]]
|
||||
local function setup_request(contents)
|
||||
stub(_G, "http", {
|
||||
checkURL = function()
|
||||
return true
|
||||
@ -9,7 +10,7 @@ describe("The wget program", function()
|
||||
get = function()
|
||||
return {
|
||||
readAll = function()
|
||||
return [[print("Hello", ...)]]
|
||||
return contents
|
||||
end,
|
||||
close = function()
|
||||
end,
|
||||
@ -19,28 +20,52 @@ describe("The wget program", function()
|
||||
end
|
||||
|
||||
it("downloads one file", function()
|
||||
setup_request()
|
||||
fs.delete("/example.com")
|
||||
setup_request(default_contents)
|
||||
|
||||
capture(stub, "wget", "https://example.com")
|
||||
|
||||
expect(fs.exists("/example.com")):eq(true)
|
||||
end)
|
||||
|
||||
it("downloads one file with given filename", function()
|
||||
setup_request()
|
||||
fs.delete("/test-files/download")
|
||||
setup_request(default_contents)
|
||||
|
||||
capture(stub, "wget", "https://example.com /test-files/download")
|
||||
|
||||
expect(fs.exists("/test-files/download")):eq(true)
|
||||
end)
|
||||
|
||||
it("downloads empty files", function()
|
||||
fs.delete("/test-files/download")
|
||||
setup_request(nil)
|
||||
|
||||
capture(stub, "wget", "https://example.com", "/test-files/download")
|
||||
|
||||
expect(fs.exists("/test-files/download")):eq(true)
|
||||
expect(fs.getSize("/test-files/download")):eq(0)
|
||||
end)
|
||||
|
||||
it("cannot save to rom", function()
|
||||
setup_request(default_contents)
|
||||
|
||||
expect(capture(stub, "wget", "https://example.com", "/rom/a-file.txt")):matches {
|
||||
ok = true,
|
||||
output = "Connecting to https://example.com... Success.\n",
|
||||
error = "Cannot save file: /rom/a-file.txt: Access denied\n",
|
||||
}
|
||||
end)
|
||||
|
||||
it("runs a program from the internet", function()
|
||||
setup_request()
|
||||
setup_request(default_contents)
|
||||
|
||||
expect(capture(stub, "wget", "run", "http://test.com", "a", "b", "c"))
|
||||
:matches { ok = true, output = "Connecting to http://test.com... Success.\nHello a b c\n", error = "" }
|
||||
end)
|
||||
|
||||
it("displays its usage when given no arguments", function()
|
||||
setup_request()
|
||||
setup_request(default_contents)
|
||||
|
||||
expect(capture(stub, "wget"))
|
||||
:matches { ok = true, output = "Usage:\nwget <url> [filename]\nwget run <url>\n", error = "" }
|
||||
|
@ -21,4 +21,24 @@ describe("The monitor program", function()
|
||||
:matches { ok = true, output = "", error = "" }
|
||||
expect(r):equals(0.5)
|
||||
end)
|
||||
|
||||
it("displays correct error messages", function()
|
||||
local r = 1
|
||||
stub(peripheral, "call", function(s, f, t) r = t end)
|
||||
stub(peripheral, "getType", function(side) return side == "left" and "monitor" or nil end)
|
||||
expect(capture(stub, "monitor", "scale", "left"))
|
||||
:matches {
|
||||
ok = true,
|
||||
output =
|
||||
"Usage:\n" ..
|
||||
" monitor <name> <program> <arguments>\n" ..
|
||||
" monitor scale <name> <scale>\n",
|
||||
error = "",
|
||||
}
|
||||
expect(capture(stub, "monitor", "scale", "top", "0.5"))
|
||||
:matches { ok = true, output = "No monitor named top\n", error = "" }
|
||||
expect(capture(stub, "monitor", "scale", "left", "aaa"))
|
||||
:matches { ok = true, output = "Invalid scale: aaa\n", error = "" }
|
||||
expect(r):equals(1)
|
||||
end)
|
||||
end)
|
||||
|
@ -19,7 +19,14 @@ describe("The craft program", function()
|
||||
stub(_G, "turtle", { craft = function() end })
|
||||
|
||||
expect(capture(stub, "/rom/programs/turtle/craft.lua"))
|
||||
:matches { ok = true, output = "Usage: /rom/programs/turtle/craft.lua [number]\n", error = "" }
|
||||
:matches { ok = true, output = "Usage: /rom/programs/turtle/craft.lua all|<number>\n", error = "" }
|
||||
end)
|
||||
|
||||
it("displays its usage when given incorrect arguments", function()
|
||||
stub(_G, "turtle", { craft = function() end })
|
||||
|
||||
expect(capture(stub, "/rom/programs/turtle/craft.lua a"))
|
||||
:matches { ok = true, output = "Usage: /rom/programs/turtle/craft.lua all|<number>\n", error = "" }
|
||||
end)
|
||||
|
||||
it("crafts multiple items", function()
|
||||
@ -52,7 +59,7 @@ describe("The craft program", function()
|
||||
:matches { ok = true, output = "1 item crafted\n", error = "" }
|
||||
end)
|
||||
|
||||
it("crafts no items", function()
|
||||
it("crafts no items", function()
|
||||
local item_count = 2
|
||||
stub(_G, "turtle", {
|
||||
craft = function()
|
||||
@ -66,4 +73,17 @@ describe("The craft program", function()
|
||||
expect(capture(stub, "/rom/programs/turtle/craft.lua 1"))
|
||||
:matches { ok = true, output = "No items crafted\n", error = "" }
|
||||
end)
|
||||
|
||||
it("crafts all items", function()
|
||||
stub(_G, "turtle", {
|
||||
craft = function()
|
||||
return true
|
||||
end,
|
||||
getItemCount = function() return 17 end,
|
||||
getSelectedSlot = function() return 1 end,
|
||||
})
|
||||
|
||||
expect(capture(stub, "/rom/programs/turtle/craft.lua all"))
|
||||
:matches { ok = true, output = "17 items crafted\n", error = "" }
|
||||
end)
|
||||
end)
|
||||
|