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`
|
- **Clone the repository:** `git clone https://github.com/SquidDev-CC/CC-Tweaked.git && cd CC-Tweaked`
|
||||||
- **Setup Forge:** `./gradlew build`
|
- **Setup Forge:** `./gradlew build`
|
||||||
- **Run Minecraft:** `./gradlew runClient` (or run the `GradleStart` class from your IDE).
|
- **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: [](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`.
|
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
|
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 {
|
buildscript {
|
||||||
repositories {
|
repositories {
|
||||||
jcenter()
|
|
||||||
mavenCentral()
|
mavenCentral()
|
||||||
maven {
|
maven {
|
||||||
name = "forge"
|
name = "forge"
|
||||||
@ -14,7 +13,7 @@ buildscript {
|
|||||||
}
|
}
|
||||||
dependencies {
|
dependencies {
|
||||||
classpath 'com.google.code.gson:gson:2.8.1'
|
classpath 'com.google.code.gson:gson:2.8.1'
|
||||||
classpath 'net.minecraftforge.gradle:ForgeGradle:4.1.9'
|
classpath 'net.minecraftforge.gradle:ForgeGradle:5.0.12'
|
||||||
classpath 'org.spongepowered:mixingradle:0.7-SNAPSHOT'
|
classpath 'org.spongepowered:mixingradle:0.7-SNAPSHOT'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -23,10 +22,11 @@ plugins {
|
|||||||
id "checkstyle"
|
id "checkstyle"
|
||||||
id "jacoco"
|
id "jacoco"
|
||||||
id "maven-publish"
|
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.matthewprenger.cursegradle" version "1.4.0"
|
||||||
id "com.github.breadmoirai.github-release" version "2.2.12"
|
id "com.github.breadmoirai.github-release" version "2.2.12"
|
||||||
id "org.jetbrains.kotlin.jvm" version "1.3.72"
|
id "org.jetbrains.kotlin.jvm" version "1.3.72"
|
||||||
|
id "com.modrinth.minotaur" version "1.2.1"
|
||||||
}
|
}
|
||||||
|
|
||||||
apply plugin: 'net.minecraftforge.gradle'
|
apply plugin: 'net.minecraftforge.gradle'
|
||||||
@ -37,15 +37,30 @@ version = mod_version
|
|||||||
group = "org.squiddev"
|
group = "org.squiddev"
|
||||||
archivesBaseName = "cc-tweaked-${mc_version}"
|
archivesBaseName = "cc-tweaked-${mc_version}"
|
||||||
|
|
||||||
|
def javaVersion = JavaLanguageVersion.of(8)
|
||||||
java {
|
java {
|
||||||
toolchain {
|
toolchain {
|
||||||
languageVersion = JavaLanguageVersion.of(8)
|
languageVersion = javaVersion
|
||||||
}
|
}
|
||||||
|
|
||||||
withSourcesJar()
|
withSourcesJar()
|
||||||
withJavadocJar()
|
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 {
|
minecraft {
|
||||||
runs {
|
runs {
|
||||||
client {
|
client {
|
||||||
@ -219,6 +234,7 @@ processResources {
|
|||||||
e.printStackTrace()
|
e.printStackTrace()
|
||||||
}
|
}
|
||||||
inputs.property "commithash", hash
|
inputs.property "commithash", hash
|
||||||
|
duplicatesStrategy = DuplicatesStrategy.INCLUDE
|
||||||
|
|
||||||
from(sourceSets.main.resources.srcDirs) {
|
from(sourceSets.main.resources.srcDirs) {
|
||||||
include 'META-INF/mods.toml'
|
include 'META-INF/mods.toml'
|
||||||
@ -235,6 +251,10 @@ processResources {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
sourcesJar {
|
||||||
|
duplicatesStrategy = DuplicatesStrategy.INCLUDE
|
||||||
|
}
|
||||||
|
|
||||||
// Web tasks
|
// Web tasks
|
||||||
|
|
||||||
|
|
||||||
@ -416,31 +436,31 @@ task checkRelease {
|
|||||||
description "Verifies that everything is ready for a release"
|
description "Verifies that everything is ready for a release"
|
||||||
|
|
||||||
inputs.property "version", mod_version
|
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/changelog.md")
|
||||||
inputs.file("src/main/resources/data/computercraft/lua/rom/help/whatsnew.txt")
|
inputs.file("src/main/resources/data/computercraft/lua/rom/help/whatsnew.md")
|
||||||
|
|
||||||
doLast {
|
doLast {
|
||||||
def ok = true
|
def ok = true
|
||||||
|
|
||||||
// Check we're targetting the current version
|
// 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") {
|
if (whatsnew[0] != "New features in CC: Tweaked $mod_version") {
|
||||||
ok = false
|
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
|
// Check "read more" exists and trim it
|
||||||
def idx = whatsnew.findIndexOf { it == 'Type "help changelog" to see the full version history.' }
|
def idx = whatsnew.findIndexOf { it == 'Type "help changelog" to see the full version history.' }
|
||||||
if (idx == -1) {
|
if (idx == -1) {
|
||||||
ok = false
|
ok = false
|
||||||
project.logger.error("Must mention the changelog in whatsnew.txt")
|
project.logger.error("Must mention the changelog in whatsnew.md")
|
||||||
} else {
|
} else {
|
||||||
whatsnew = whatsnew.getAt(0..<idx)
|
whatsnew = whatsnew.getAt(0..<idx)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check whatsnew and changelog match.
|
// Check whatsnew and changelog match.
|
||||||
def versionChangelog = "# " + whatsnew.join("\n")
|
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)) {
|
if (!changelog.startsWith(versionChangelog)) {
|
||||||
ok = false
|
ok = false
|
||||||
project.logger.error("whatsnew and changelog are not in sync")
|
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) {
|
tasks.withType(GenerateModuleMetadata) {
|
||||||
// We can't generate metadata as that includes Forge as a dependency.
|
// We can't generate metadata as that includes Forge as a dependency.
|
||||||
enabled = false
|
enabled = false
|
||||||
@ -538,10 +573,10 @@ githubRelease {
|
|||||||
prerelease false
|
prerelease false
|
||||||
}
|
}
|
||||||
|
|
||||||
def uploadTasks = ["publish", "curseforge", "githubRelease"]
|
def uploadTasks = ["publish", "curseforge", "publishModrinth", "githubRelease"]
|
||||||
uploadTasks.forEach { tasks.getByName(it).dependsOn checkRelease }
|
uploadTasks.forEach { tasks.getByName(it).dependsOn checkRelease }
|
||||||
|
|
||||||
task uploadAll(dependsOn: uploadTasks) {
|
task uploadAll(dependsOn: uploadTasks) {
|
||||||
group "upload"
|
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=".*[\\/]ComputerCraft.java" />
|
||||||
<suppress checks="StaticVariableName" files=".*[\\/]ComputerCraftAPI.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. -->
|
<!-- The commands API is documented in Lua. -->
|
||||||
<suppress checks="SummaryJavadocCheck" files=".*[\\/]CommandAPI.java" />
|
<suppress checks="SummaryJavadocCheck" files=".*[\\/]CommandAPI.java" />
|
||||||
</suppressions>
|
</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
|
distributionBase=GRADLE_USER_HOME
|
||||||
distributionPath=wrapper/dists
|
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
|
zipStoreBase=GRADLE_USER_HOME
|
||||||
zipStorePath=wrapper/dists
|
zipStorePath=wrapper/dists
|
||||||
|
@ -32,7 +32,7 @@ public final class TransformedModel
|
|||||||
public TransformedModel( @Nonnull IBakedModel model )
|
public TransformedModel( @Nonnull IBakedModel model )
|
||||||
{
|
{
|
||||||
this.model = Objects.requireNonNull( model );
|
this.model = Objects.requireNonNull( model );
|
||||||
this.matrix = TransformationMatrix.identity();
|
matrix = TransformationMatrix.identity();
|
||||||
}
|
}
|
||||||
|
|
||||||
public static TransformedModel of( @Nonnull ModelResourceLocation location )
|
public static TransformedModel of( @Nonnull ModelResourceLocation location )
|
||||||
|
@ -30,7 +30,7 @@ public class FileOperationException extends IOException
|
|||||||
public FileOperationException( @Nonnull String message )
|
public FileOperationException( @Nonnull String message )
|
||||||
{
|
{
|
||||||
super( Objects.requireNonNull( message, "message cannot be null" ) );
|
super( Objects.requireNonNull( message, "message cannot be null" ) );
|
||||||
this.filename = null;
|
filename = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Nullable
|
@Nullable
|
||||||
|
@ -19,14 +19,14 @@ public class LuaException extends Exception
|
|||||||
public LuaException( @Nullable String message )
|
public LuaException( @Nullable String message )
|
||||||
{
|
{
|
||||||
super( message );
|
super( message );
|
||||||
this.hasLevel = false;
|
hasLevel = false;
|
||||||
this.level = 1;
|
level = 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
public LuaException( @Nullable String message, int level )
|
public LuaException( @Nullable String message, int level )
|
||||||
{
|
{
|
||||||
super( message );
|
super( message );
|
||||||
this.hasLevel = true;
|
hasLevel = true;
|
||||||
this.level = level;
|
this.level = level;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -30,14 +30,14 @@ public final class MethodResult
|
|||||||
|
|
||||||
private MethodResult( Object[] arguments, ILuaCallback callback )
|
private MethodResult( Object[] arguments, ILuaCallback callback )
|
||||||
{
|
{
|
||||||
this.result = arguments;
|
result = arguments;
|
||||||
this.callback = callback;
|
this.callback = callback;
|
||||||
this.adjust = 0;
|
adjust = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
private MethodResult( Object[] arguments, ILuaCallback callback, int adjust )
|
private MethodResult( Object[] arguments, ILuaCallback callback, int adjust )
|
||||||
{
|
{
|
||||||
this.result = arguments;
|
result = arguments;
|
||||||
this.callback = callback;
|
this.callback = callback;
|
||||||
this.adjust = adjust;
|
this.adjust = adjust;
|
||||||
}
|
}
|
||||||
|
@ -22,6 +22,7 @@ public class ClientHooks
|
|||||||
if( event.getWorld().isClientSide() )
|
if( event.getWorld().isClientSide() )
|
||||||
{
|
{
|
||||||
ClientMonitor.destroyAll();
|
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.matrix.MatrixStack;
|
||||||
import com.mojang.blaze3d.systems.RenderSystem;
|
import com.mojang.blaze3d.systems.RenderSystem;
|
||||||
import dan200.computercraft.ComputerCraft;
|
import dan200.computercraft.ComputerCraft;
|
||||||
|
import dan200.computercraft.client.gui.widgets.ComputerSidebar;
|
||||||
import dan200.computercraft.client.gui.widgets.WidgetTerminal;
|
import dan200.computercraft.client.gui.widgets.WidgetTerminal;
|
||||||
import dan200.computercraft.client.gui.widgets.WidgetWrapper;
|
|
||||||
import dan200.computercraft.client.render.ComputerBorderRenderer;
|
import dan200.computercraft.client.render.ComputerBorderRenderer;
|
||||||
import dan200.computercraft.shared.computer.core.ClientComputer;
|
import dan200.computercraft.shared.computer.core.ClientComputer;
|
||||||
import dan200.computercraft.shared.computer.core.ComputerFamily;
|
import dan200.computercraft.shared.computer.core.ComputerFamily;
|
||||||
@ -25,7 +25,6 @@ import org.lwjgl.glfw.GLFW;
|
|||||||
import javax.annotation.Nonnull;
|
import javax.annotation.Nonnull;
|
||||||
|
|
||||||
import static dan200.computercraft.client.render.ComputerBorderRenderer.BORDER;
|
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>
|
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 termWidth;
|
||||||
private final int termHeight;
|
private final int termHeight;
|
||||||
|
|
||||||
private WidgetTerminal terminal;
|
private WidgetTerminal terminal = null;
|
||||||
private WidgetWrapper terminalWrapper;
|
|
||||||
|
|
||||||
private GuiComputer(
|
private GuiComputer(
|
||||||
T container, PlayerInventory player, ITextComponent title, int termWidth, int termHeight
|
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();
|
computer = (ClientComputer) container.getComputer();
|
||||||
this.termWidth = termWidth;
|
this.termWidth = termWidth;
|
||||||
this.termHeight = termHeight;
|
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 )
|
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
|
@Override
|
||||||
protected void init()
|
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();
|
super.init();
|
||||||
|
|
||||||
terminal = new WidgetTerminal( minecraft, () -> computer, termWidth, termHeight, MARGIN, MARGIN, MARGIN, MARGIN );
|
minecraft.keyboardHandler.setSendRepeatsToGui( true );
|
||||||
terminalWrapper = new WidgetWrapper( terminal, MARGIN + BORDER + leftPos, MARGIN + BORDER + topPos, termPxWidth, termPxHeight );
|
|
||||||
|
|
||||||
children.add( terminalWrapper );
|
terminal = addButton( new WidgetTerminal( computer,
|
||||||
setFocused( terminalWrapper );
|
leftPos + ComputerSidebar.WIDTH + BORDER, topPos + BORDER, termWidth, termHeight
|
||||||
|
) );
|
||||||
|
ComputerSidebar.addButtons( this, computer, this::addButton, leftPos, topPos + BORDER );
|
||||||
|
setFocused( terminal );
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void removed()
|
public void removed()
|
||||||
{
|
{
|
||||||
super.removed();
|
super.removed();
|
||||||
children.remove( terminal );
|
|
||||||
terminal = null;
|
|
||||||
minecraft.keyboardHandler.setSendRepeatsToGui( false );
|
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 )
|
public boolean keyPressed( int key, int scancode, int modifiers )
|
||||||
{
|
{
|
||||||
// Forward the tab key to the terminal, rather than moving between controls.
|
// 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 );
|
return getFocused().keyPressed( key, scancode, modifiers );
|
||||||
}
|
}
|
||||||
@ -125,16 +117,11 @@ public final class GuiComputer<T extends ContainerComputerBase> extends Containe
|
|||||||
@Override
|
@Override
|
||||||
public void renderBg( @Nonnull MatrixStack stack, float partialTicks, int mouseX, int mouseY )
|
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
|
// Draw a border around the terminal
|
||||||
RenderSystem.color4f( 1, 1, 1, 1 );
|
RenderSystem.color4f( 1, 1, 1, 1 );
|
||||||
minecraft.getTextureManager().bind( ComputerBorderRenderer.getTexture( family ) );
|
minecraft.getTextureManager().bind( ComputerBorderRenderer.getTexture( family ) );
|
||||||
ComputerBorderRenderer.render(
|
ComputerBorderRenderer.render( terminal.x, terminal.y, getBlitOffset(), terminal.getWidth(), terminal.getHeight() );
|
||||||
terminalWrapper.getX() - MARGIN, terminalWrapper.getY() - MARGIN, getBlitOffset(),
|
ComputerSidebar.renderBackground( stack, leftPos, topPos + BORDER );
|
||||||
terminalWrapper.getWidth() + MARGIN * 2, terminalWrapper.getHeight() + MARGIN * 2
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -8,8 +8,9 @@ package dan200.computercraft.client.gui;
|
|||||||
import com.mojang.blaze3d.matrix.MatrixStack;
|
import com.mojang.blaze3d.matrix.MatrixStack;
|
||||||
import com.mojang.blaze3d.systems.RenderSystem;
|
import com.mojang.blaze3d.systems.RenderSystem;
|
||||||
import dan200.computercraft.ComputerCraft;
|
import dan200.computercraft.ComputerCraft;
|
||||||
|
import dan200.computercraft.client.gui.widgets.ComputerSidebar;
|
||||||
import dan200.computercraft.client.gui.widgets.WidgetTerminal;
|
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.ClientComputer;
|
||||||
import dan200.computercraft.shared.computer.core.ComputerFamily;
|
import dan200.computercraft.shared.computer.core.ComputerFamily;
|
||||||
import dan200.computercraft.shared.turtle.inventory.ContainerTurtle;
|
import dan200.computercraft.shared.turtle.inventory.ContainerTurtle;
|
||||||
@ -21,6 +22,8 @@ import org.lwjgl.glfw.GLFW;
|
|||||||
|
|
||||||
import javax.annotation.Nonnull;
|
import javax.annotation.Nonnull;
|
||||||
|
|
||||||
|
import static dan200.computercraft.shared.turtle.inventory.ContainerTurtle.*;
|
||||||
|
|
||||||
public class GuiTurtle extends ContainerScreen<ContainerTurtle>
|
public class GuiTurtle extends ContainerScreen<ContainerTurtle>
|
||||||
{
|
{
|
||||||
private static final ResourceLocation BACKGROUND_NORMAL = new ResourceLocation( ComputerCraft.MOD_ID, "textures/gui/turtle_normal.png" );
|
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 final ClientComputer computer;
|
||||||
|
|
||||||
private WidgetTerminal terminal;
|
private WidgetTerminal terminal;
|
||||||
private WidgetWrapper terminalWrapper;
|
|
||||||
|
|
||||||
public GuiTurtle( ContainerTurtle container, PlayerInventory player, ITextComponent title )
|
public GuiTurtle( ContainerTurtle container, PlayerInventory player, ITextComponent title )
|
||||||
{
|
{
|
||||||
@ -52,27 +54,18 @@ public class GuiTurtle extends ContainerScreen<ContainerTurtle>
|
|||||||
super.init();
|
super.init();
|
||||||
minecraft.keyboardHandler.setSendRepeatsToGui( true );
|
minecraft.keyboardHandler.setSendRepeatsToGui( true );
|
||||||
|
|
||||||
int termPxWidth = ComputerCraft.turtleTermWidth * FixedWidthFontRenderer.FONT_WIDTH;
|
terminal = addButton( new WidgetTerminal(
|
||||||
int termPxHeight = ComputerCraft.turtleTermHeight * FixedWidthFontRenderer.FONT_HEIGHT;
|
computer, leftPos + BORDER + ComputerSidebar.WIDTH, topPos + BORDER,
|
||||||
|
ComputerCraft.turtleTermWidth, ComputerCraft.turtleTermHeight
|
||||||
terminal = new WidgetTerminal(
|
) );
|
||||||
minecraft, () -> computer,
|
ComputerSidebar.addButtons( this, computer, this::addButton, leftPos, topPos + BORDER );
|
||||||
ComputerCraft.turtleTermWidth,
|
setFocused( terminal );
|
||||||
ComputerCraft.turtleTermHeight,
|
|
||||||
2, 2, 2, 2
|
|
||||||
);
|
|
||||||
terminalWrapper = new WidgetWrapper( terminal, 2 + 8 + leftPos, 2 + 8 + topPos, termPxWidth, termPxHeight );
|
|
||||||
|
|
||||||
children.add( terminalWrapper );
|
|
||||||
setFocused( terminalWrapper );
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void removed()
|
public void removed()
|
||||||
{
|
{
|
||||||
super.removed();
|
super.removed();
|
||||||
children.remove( terminal );
|
|
||||||
terminal = null;
|
|
||||||
minecraft.keyboardHandler.setSendRepeatsToGui( false );
|
minecraft.keyboardHandler.setSendRepeatsToGui( false );
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -87,7 +80,7 @@ public class GuiTurtle extends ContainerScreen<ContainerTurtle>
|
|||||||
public boolean keyPressed( int key, int scancode, int modifiers )
|
public boolean keyPressed( int key, int scancode, int modifiers )
|
||||||
{
|
{
|
||||||
// Forward the tab key to the terminal, rather than moving between controls.
|
// 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 );
|
return getFocused().keyPressed( key, scancode, modifiers );
|
||||||
}
|
}
|
||||||
@ -98,24 +91,21 @@ public class GuiTurtle extends ContainerScreen<ContainerTurtle>
|
|||||||
@Override
|
@Override
|
||||||
protected void renderBg( @Nonnull MatrixStack transform, float partialTicks, int mouseX, int mouseY )
|
protected void renderBg( @Nonnull MatrixStack transform, float partialTicks, int mouseX, int mouseY )
|
||||||
{
|
{
|
||||||
// Draw term
|
boolean advanced = family == ComputerFamily.ADVANCED;
|
||||||
ResourceLocation texture = family == ComputerFamily.ADVANCED ? BACKGROUND_ADVANCED : BACKGROUND_NORMAL;
|
minecraft.getTextureManager().bind( advanced ? BACKGROUND_ADVANCED : BACKGROUND_NORMAL );
|
||||||
terminal.draw( terminalWrapper.getX(), terminalWrapper.getY() );
|
blit( transform, leftPos + ComputerSidebar.WIDTH, topPos, 0, 0, imageWidth, imageHeight );
|
||||||
|
|
||||||
// Draw border/inventory
|
minecraft.getTextureManager().bind( advanced ? ComputerBorderRenderer.BACKGROUND_ADVANCED : ComputerBorderRenderer.BACKGROUND_NORMAL );
|
||||||
RenderSystem.color4f( 1.0F, 1.0F, 1.0F, 1.0F );
|
ComputerSidebar.renderBackground( transform, leftPos, topPos + BORDER );
|
||||||
minecraft.getTextureManager().bind( texture );
|
|
||||||
blit( transform, leftPos, topPos, 0, 0, imageWidth, imageHeight );
|
|
||||||
|
|
||||||
// Draw selection slot
|
|
||||||
int slot = container.getSelectedSlot();
|
int slot = container.getSelectedSlot();
|
||||||
if( slot >= 0 )
|
if( slot >= 0 )
|
||||||
{
|
{
|
||||||
|
RenderSystem.color4f( 1.0F, 1.0F, 1.0F, 1.0F );
|
||||||
int slotX = slot % 4;
|
int slotX = slot % 4;
|
||||||
int slotY = slot / 4;
|
int slotY = slot / 4;
|
||||||
blit( transform,
|
blit( transform,
|
||||||
leftPos + ContainerTurtle.TURTLE_START_X - 2 + slotX * 18,
|
leftPos + TURTLE_START_X - 2 + slotX * 18, topPos + PLAYER_START_Y - 2 + slotY * 18,
|
||||||
topPos + ContainerTurtle.PLAYER_START_Y - 2 + slotY * 18,
|
|
||||||
0, 217, 24, 24
|
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;
|
package dan200.computercraft.client.gui.widgets;
|
||||||
|
|
||||||
|
import com.mojang.blaze3d.matrix.MatrixStack;
|
||||||
import dan200.computercraft.client.gui.FixedWidthFontRenderer;
|
import dan200.computercraft.client.gui.FixedWidthFontRenderer;
|
||||||
import dan200.computercraft.core.terminal.Terminal;
|
import dan200.computercraft.core.terminal.Terminal;
|
||||||
import dan200.computercraft.shared.computer.core.ClientComputer;
|
import dan200.computercraft.shared.computer.core.ClientComputer;
|
||||||
import dan200.computercraft.shared.computer.core.IComputer;
|
|
||||||
import net.minecraft.client.Minecraft;
|
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.SharedConstants;
|
||||||
|
import net.minecraft.util.math.vector.Matrix4f;
|
||||||
|
import net.minecraft.util.text.StringTextComponent;
|
||||||
import org.lwjgl.glfw.GLFW;
|
import org.lwjgl.glfw.GLFW;
|
||||||
|
|
||||||
|
import javax.annotation.Nonnull;
|
||||||
import java.util.BitSet;
|
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_HEIGHT;
|
||||||
import static dan200.computercraft.client.gui.FixedWidthFontRenderer.FONT_WIDTH;
|
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 static final float TERMINATE_TIME = 0.5f;
|
||||||
|
|
||||||
private final Minecraft client;
|
private final ClientComputer computer;
|
||||||
|
|
||||||
private boolean focused;
|
// The positions of the actual terminal
|
||||||
|
private final int innerX;
|
||||||
private final Supplier<ClientComputer> computer;
|
private final int innerY;
|
||||||
private final int termWidth;
|
private final int innerWidth;
|
||||||
private final int termHeight;
|
private final int innerHeight;
|
||||||
|
|
||||||
private float terminateTimer = -1;
|
private float terminateTimer = -1;
|
||||||
private float rebootTimer = -1;
|
private float rebootTimer = -1;
|
||||||
@ -40,23 +43,18 @@ public class WidgetTerminal implements IGuiEventListener
|
|||||||
private int lastMouseX = -1;
|
private int lastMouseX = -1;
|
||||||
private int lastMouseY = -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 );
|
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.computer = computer;
|
||||||
this.termWidth = termWidth;
|
|
||||||
this.termHeight = termHeight;
|
innerX = x + MARGIN;
|
||||||
this.leftMargin = leftMargin;
|
innerY = y + MARGIN;
|
||||||
this.rightMargin = rightMargin;
|
innerWidth = termWidth * FONT_WIDTH;
|
||||||
this.topMargin = topMargin;
|
innerHeight = termHeight * FONT_HEIGHT;
|
||||||
this.bottomMargin = bottomMargin;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -65,7 +63,7 @@ public class WidgetTerminal implements IGuiEventListener
|
|||||||
if( ch >= 32 && ch <= 126 || ch >= 160 && ch <= 255 ) // printable chars in byte range
|
if( ch >= 32 && ch <= 126 || ch >= 160 && ch <= 255 ) // printable chars in byte range
|
||||||
{
|
{
|
||||||
// Queue the "char" event
|
// Queue the "char" event
|
||||||
queueEvent( "char", Character.toString( ch ) );
|
computer.queueEvent( "char", new Object[] { Character.toString( ch ) } );
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
@ -91,7 +89,7 @@ public class WidgetTerminal implements IGuiEventListener
|
|||||||
|
|
||||||
case GLFW.GLFW_KEY_V:
|
case GLFW.GLFW_KEY_V:
|
||||||
// Ctrl+V for paste
|
// Ctrl+V for paste
|
||||||
String clipboard = client.keyboardHandler.getClipboard();
|
String clipboard = Minecraft.getInstance().keyboardHandler.getClipboard();
|
||||||
if( clipboard != null )
|
if( clipboard != null )
|
||||||
{
|
{
|
||||||
// Clip to the first occurrence of \r or \n
|
// 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
|
// Clip to 512 characters and queue the event
|
||||||
if( clipboard.length() > 512 ) clipboard = clipboard.substring( 0, 512 );
|
if( clipboard.length() > 512 ) clipboard = clipboard.substring( 0, 512 );
|
||||||
queueEvent( "paste", clipboard );
|
computer.queueEvent( "paste", new Object[] { clipboard } );
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
@ -129,8 +127,7 @@ public class WidgetTerminal implements IGuiEventListener
|
|||||||
// Queue the "key" event and add to the down set
|
// Queue the "key" event and add to the down set
|
||||||
boolean repeat = keysDown.get( key );
|
boolean repeat = keysDown.get( key );
|
||||||
keysDown.set( key );
|
keysDown.set( key );
|
||||||
IComputer computer = this.computer.get();
|
computer.keyDown( key, repeat );
|
||||||
if( computer != null ) computer.keyDown( key, repeat );
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
@ -143,8 +140,7 @@ public class WidgetTerminal implements IGuiEventListener
|
|||||||
if( key >= 0 && keysDown.get( key ) )
|
if( key >= 0 && keysDown.get( key ) )
|
||||||
{
|
{
|
||||||
keysDown.set( key, false );
|
keysDown.set( key, false );
|
||||||
IComputer computer = this.computer.get();
|
computer.keyUp( key );
|
||||||
if( computer != null ) computer.keyUp( key );
|
|
||||||
}
|
}
|
||||||
|
|
||||||
switch( key )
|
switch( key )
|
||||||
@ -170,14 +166,14 @@ public class WidgetTerminal implements IGuiEventListener
|
|||||||
@Override
|
@Override
|
||||||
public boolean mouseClicked( double mouseX, double mouseY, int button )
|
public boolean mouseClicked( double mouseX, double mouseY, int button )
|
||||||
{
|
{
|
||||||
ClientComputer computer = this.computer.get();
|
if( !inTermRegion( mouseX, mouseY ) ) return false;
|
||||||
if( computer == null || !computer.isColour() || button < 0 || button > 2 ) return false;
|
if( !computer.isColour() || button < 0 || button > 2 ) return false;
|
||||||
|
|
||||||
Terminal term = computer.getTerminal();
|
Terminal term = computer.getTerminal();
|
||||||
if( term != null )
|
if( term != null )
|
||||||
{
|
{
|
||||||
int charX = (int) (mouseX / FONT_WIDTH);
|
int charX = (int) ((mouseX - x) / FONT_WIDTH);
|
||||||
int charY = (int) (mouseY / FONT_HEIGHT);
|
int charY = (int) ((mouseY - x) / FONT_HEIGHT);
|
||||||
charX = Math.min( Math.max( charX, 0 ), term.getWidth() - 1 );
|
charX = Math.min( Math.max( charX, 0 ), term.getWidth() - 1 );
|
||||||
charY = Math.min( Math.max( charY, 0 ), term.getHeight() - 1 );
|
charY = Math.min( Math.max( charY, 0 ), term.getHeight() - 1 );
|
||||||
|
|
||||||
@ -194,14 +190,14 @@ public class WidgetTerminal implements IGuiEventListener
|
|||||||
@Override
|
@Override
|
||||||
public boolean mouseReleased( double mouseX, double mouseY, int button )
|
public boolean mouseReleased( double mouseX, double mouseY, int button )
|
||||||
{
|
{
|
||||||
ClientComputer computer = this.computer.get();
|
if( !inTermRegion( mouseX, mouseY ) ) return false;
|
||||||
if( computer == null || !computer.isColour() || button < 0 || button > 2 ) return false;
|
if( !computer.isColour() || button < 0 || button > 2 ) return false;
|
||||||
|
|
||||||
Terminal term = computer.getTerminal();
|
Terminal term = computer.getTerminal();
|
||||||
if( term != null )
|
if( term != null )
|
||||||
{
|
{
|
||||||
int charX = (int) (mouseX / FONT_WIDTH);
|
int charX = (int) ((mouseX - x) / FONT_WIDTH);
|
||||||
int charY = (int) (mouseY / FONT_HEIGHT);
|
int charY = (int) ((mouseY - x) / FONT_HEIGHT);
|
||||||
charX = Math.min( Math.max( charX, 0 ), term.getWidth() - 1 );
|
charX = Math.min( Math.max( charX, 0 ), term.getWidth() - 1 );
|
||||||
charY = Math.min( Math.max( charY, 0 ), term.getHeight() - 1 );
|
charY = Math.min( Math.max( charY, 0 ), term.getHeight() - 1 );
|
||||||
|
|
||||||
@ -221,14 +217,14 @@ public class WidgetTerminal implements IGuiEventListener
|
|||||||
@Override
|
@Override
|
||||||
public boolean mouseDragged( double mouseX, double mouseY, int button, double v2, double v3 )
|
public boolean mouseDragged( double mouseX, double mouseY, int button, double v2, double v3 )
|
||||||
{
|
{
|
||||||
ClientComputer computer = this.computer.get();
|
if( !inTermRegion( mouseX, mouseY ) ) return false;
|
||||||
if( computer == null || !computer.isColour() || button < 0 || button > 2 ) return false;
|
if( !computer.isColour() || button < 0 || button > 2 ) return false;
|
||||||
|
|
||||||
Terminal term = computer.getTerminal();
|
Terminal term = computer.getTerminal();
|
||||||
if( term != null )
|
if( term != null )
|
||||||
{
|
{
|
||||||
int charX = (int) (mouseX / FONT_WIDTH);
|
int charX = (int) ((mouseX - x) / FONT_WIDTH);
|
||||||
int charY = (int) (mouseY / FONT_HEIGHT);
|
int charY = (int) ((mouseY - x) / FONT_HEIGHT);
|
||||||
charX = Math.min( Math.max( charX, 0 ), term.getWidth() - 1 );
|
charX = Math.min( Math.max( charX, 0 ), term.getWidth() - 1 );
|
||||||
charY = Math.min( Math.max( charY, 0 ), term.getHeight() - 1 );
|
charY = Math.min( Math.max( charY, 0 ), term.getHeight() - 1 );
|
||||||
|
|
||||||
@ -246,14 +242,14 @@ public class WidgetTerminal implements IGuiEventListener
|
|||||||
@Override
|
@Override
|
||||||
public boolean mouseScrolled( double mouseX, double mouseY, double delta )
|
public boolean mouseScrolled( double mouseX, double mouseY, double delta )
|
||||||
{
|
{
|
||||||
ClientComputer computer = this.computer.get();
|
if( !inTermRegion( mouseX, mouseY ) ) return false;
|
||||||
if( computer == null || !computer.isColour() || delta == 0 ) return false;
|
if( !computer.isColour() || delta == 0 ) return false;
|
||||||
|
|
||||||
Terminal term = computer.getTerminal();
|
Terminal term = computer.getTerminal();
|
||||||
if( term != null )
|
if( term != null )
|
||||||
{
|
{
|
||||||
int charX = (int) (mouseX / FONT_WIDTH);
|
int charX = (int) ((mouseX - innerX) / FONT_WIDTH);
|
||||||
int charY = (int) (mouseY / FONT_HEIGHT);
|
int charY = (int) ((mouseY - innerY) / FONT_HEIGHT);
|
||||||
charX = Math.min( Math.max( charX, 0 ), term.getWidth() - 1 );
|
charX = Math.min( Math.max( charX, 0 ), term.getWidth() - 1 );
|
||||||
charY = Math.min( Math.max( charY, 0 ), term.getHeight() - 1 );
|
charY = Math.min( Math.max( charY, 0 ), term.getHeight() - 1 );
|
||||||
|
|
||||||
@ -266,89 +262,74 @@ public class WidgetTerminal implements IGuiEventListener
|
|||||||
return true;
|
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()
|
public void update()
|
||||||
{
|
{
|
||||||
if( terminateTimer >= 0 && terminateTimer < TERMINATE_TIME && (terminateTimer += 0.05f) > TERMINATE_TIME )
|
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 )
|
if( shutdownTimer >= 0 && shutdownTimer < TERMINATE_TIME && (shutdownTimer += 0.05f) > TERMINATE_TIME )
|
||||||
{
|
{
|
||||||
ClientComputer computer = this.computer.get();
|
computer.shutdown();
|
||||||
if( computer != null ) computer.shutdown();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if( rebootTimer >= 0 && rebootTimer < TERMINATE_TIME && (rebootTimer += 0.05f) > TERMINATE_TIME )
|
if( rebootTimer >= 0 && rebootTimer < TERMINATE_TIME && (rebootTimer += 0.05f) > TERMINATE_TIME )
|
||||||
{
|
{
|
||||||
ClientComputer computer = this.computer.get();
|
computer.reboot();
|
||||||
if( computer != null ) computer.reboot();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean changeFocus( boolean reversed )
|
public void onFocusedChanged( boolean focused )
|
||||||
{
|
{
|
||||||
if( focused )
|
if( !focused )
|
||||||
{
|
{
|
||||||
// When blurring, we should make all keys go up
|
// When blurring, we should make all keys go up
|
||||||
for( int key = 0; key < keysDown.size(); key++ )
|
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();
|
keysDown.clear();
|
||||||
|
|
||||||
// When blurring, we should make the last mouse button go up
|
// When blurring, we should make the last mouse button go up
|
||||||
if( lastMouseButton > 0 )
|
if( lastMouseButton > 0 )
|
||||||
{
|
{
|
||||||
IComputer computer = this.computer.get();
|
computer.mouseUp( lastMouseButton + 1, lastMouseX + 1, lastMouseY + 1 );
|
||||||
if( computer != null ) computer.mouseUp( lastMouseButton + 1, lastMouseX + 1, lastMouseY + 1 );
|
|
||||||
lastMouseButton = -1;
|
lastMouseButton = -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
shutdownTimer = terminateTimer = rebootTimer = -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
|
@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;
|
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 Matrix4f transform;
|
||||||
private final IVertexBuilder builder;
|
private final IVertexBuilder builder;
|
||||||
|
@ -37,7 +37,7 @@ public class HTTPAPI implements ILuaAPI
|
|||||||
{
|
{
|
||||||
private final IAPIEnvironment apiEnvironment;
|
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<HttpRequest> requests = new ResourceQueue<>( () -> ComputerCraft.httpMaxRequests );
|
||||||
private final ResourceGroup<Websocket> websockets = new ResourceGroup<>( () -> ComputerCraft.httpMaxWebsockets );
|
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 );
|
HttpRequest request = new HttpRequest( requests, apiEnvironment, address, postString, headers, binary, redirect );
|
||||||
|
|
||||||
// Make the request
|
// 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 };
|
return new Object[] { true };
|
||||||
}
|
}
|
||||||
@ -138,12 +141,15 @@ public class HTTPAPI implements ILuaAPI
|
|||||||
}
|
}
|
||||||
|
|
||||||
@LuaFunction
|
@LuaFunction
|
||||||
public final Object[] checkURL( String address )
|
public final Object[] checkURL( String address ) throws LuaException
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
URI uri = HttpRequest.checkUri( address );
|
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 };
|
return new Object[] { true };
|
||||||
}
|
}
|
||||||
|
@ -97,7 +97,7 @@ public abstract class Resource<T extends Resource<T>> implements Closeable
|
|||||||
tryClose();
|
tryClose();
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean queue( Consumer<T> task )
|
public final boolean queue( Consumer<T> task )
|
||||||
{
|
{
|
||||||
@SuppressWarnings( "unchecked" )
|
@SuppressWarnings( "unchecked" )
|
||||||
T thisT = (T) this;
|
T thisT = (T) this;
|
||||||
|
@ -18,6 +18,9 @@ import java.util.function.Supplier;
|
|||||||
*/
|
*/
|
||||||
public class ResourceGroup<T extends Resource<T>>
|
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;
|
private static final IntSupplier ZERO = () -> 0;
|
||||||
|
|
||||||
final IntSupplier limit;
|
final IntSupplier limit;
|
||||||
|
@ -38,8 +38,10 @@ public class ResourceQueue<T extends Resource<T>> extends ResourceGroup<T>
|
|||||||
public synchronized boolean queue( Supplier<T> resource )
|
public synchronized boolean queue( Supplier<T> resource )
|
||||||
{
|
{
|
||||||
if( !active ) return false;
|
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;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -11,7 +11,10 @@ import com.google.common.cache.LoadingCache;
|
|||||||
import com.google.common.primitives.Primitives;
|
import com.google.common.primitives.Primitives;
|
||||||
import com.google.common.reflect.TypeToken;
|
import com.google.common.reflect.TypeToken;
|
||||||
import dan200.computercraft.ComputerCraft;
|
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.ClassWriter;
|
||||||
import org.objectweb.asm.MethodVisitor;
|
import org.objectweb.asm.MethodVisitor;
|
||||||
import org.objectweb.asm.Type;
|
import org.objectweb.asm.Type;
|
||||||
@ -63,7 +66,7 @@ public final class Generator<T>
|
|||||||
{
|
{
|
||||||
this.base = base;
|
this.base = base;
|
||||||
this.context = context;
|
this.context = context;
|
||||||
this.interfaces = new String[] { Type.getInternalName( base ) };
|
interfaces = new String[] { Type.getInternalName( base ) };
|
||||||
this.wrap = wrap;
|
this.wrap = wrap;
|
||||||
|
|
||||||
StringBuilder methodDesc = new StringBuilder().append( "(Ljava/lang/Object;" );
|
StringBuilder methodDesc = new StringBuilder().append( "(Ljava/lang/Object;" );
|
||||||
|
@ -251,6 +251,20 @@ final class ComputerExecutor
|
|||||||
* and then schedule a shutdown.
|
* and then schedule a shutdown.
|
||||||
*/
|
*/
|
||||||
void abort()
|
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;
|
ILuaMachine machine = this.machine;
|
||||||
if( machine != null ) machine.close();
|
if( machine != null ) machine.close();
|
||||||
@ -258,7 +272,7 @@ final class ComputerExecutor
|
|||||||
synchronized( queueLock )
|
synchronized( queueLock )
|
||||||
{
|
{
|
||||||
if( closed ) return;
|
if( closed ) return;
|
||||||
command = StateCommand.ABORT;
|
this.command = command;
|
||||||
if( isOn ) enqueue();
|
if( isOn ) enqueue();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -596,6 +610,12 @@ final class ComputerExecutor
|
|||||||
displayFailure( "Error running computer", TimeoutState.ABORT_MESSAGE );
|
displayFailure( "Error running computer", TimeoutState.ABORT_MESSAGE );
|
||||||
shutdown();
|
shutdown();
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
case ERROR:
|
||||||
|
if( !isOn ) return;
|
||||||
|
displayFailure( "Error running computer", "An internal error occurred, see logs." );
|
||||||
|
shutdown();
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else if( event != null )
|
else if( event != null )
|
||||||
@ -644,6 +664,7 @@ final class ComputerExecutor
|
|||||||
SHUTDOWN,
|
SHUTDOWN,
|
||||||
REBOOT,
|
REBOOT,
|
||||||
ABORT,
|
ABORT,
|
||||||
|
ERROR,
|
||||||
}
|
}
|
||||||
|
|
||||||
private static final class Event
|
private static final class Event
|
||||||
|
@ -506,6 +506,8 @@ public final class ComputerThread
|
|||||||
catch( Exception | LinkageError | VirtualMachineError e )
|
catch( Exception | LinkageError | VirtualMachineError e )
|
||||||
{
|
{
|
||||||
ComputerCraft.log.error( "Error running task on computer #" + executor.getComputer().getID(), 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
|
finally
|
||||||
{
|
{
|
||||||
|
@ -10,7 +10,6 @@ import dan200.computercraft.api.lua.*;
|
|||||||
import dan200.computercraft.core.asm.LuaMethod;
|
import dan200.computercraft.core.asm.LuaMethod;
|
||||||
import dan200.computercraft.core.asm.ObjectSource;
|
import dan200.computercraft.core.asm.ObjectSource;
|
||||||
import dan200.computercraft.core.computer.Computer;
|
import dan200.computercraft.core.computer.Computer;
|
||||||
import dan200.computercraft.core.computer.MainThread;
|
|
||||||
import dan200.computercraft.core.computer.TimeoutState;
|
import dan200.computercraft.core.computer.TimeoutState;
|
||||||
import dan200.computercraft.core.tracking.Tracking;
|
import dan200.computercraft.core.tracking.Tracking;
|
||||||
import dan200.computercraft.core.tracking.TrackingField;
|
import dan200.computercraft.core.tracking.TrackingField;
|
||||||
@ -53,7 +52,7 @@ public class CobaltLuaMachine implements ILuaMachine
|
|||||||
private final Computer computer;
|
private final Computer computer;
|
||||||
private final TimeoutState timeout;
|
private final TimeoutState timeout;
|
||||||
private final TimeoutDebugHandler debug;
|
private final TimeoutDebugHandler debug;
|
||||||
private final ILuaContext context = new CobaltLuaContext();
|
private final ILuaContext context;
|
||||||
|
|
||||||
private LuaState state;
|
private LuaState state;
|
||||||
private LuaTable globals;
|
private LuaTable globals;
|
||||||
@ -65,6 +64,7 @@ public class CobaltLuaMachine implements ILuaMachine
|
|||||||
{
|
{
|
||||||
this.computer = computer;
|
this.computer = computer;
|
||||||
this.timeout = timeout;
|
this.timeout = timeout;
|
||||||
|
context = new LuaContext( computer );
|
||||||
debug = new TimeoutDebugHandler();
|
debug = new TimeoutDebugHandler();
|
||||||
|
|
||||||
// Create an environment to run in
|
// 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 class HardAbortError extends Error
|
||||||
{
|
{
|
||||||
private static final long serialVersionUID = 7954092008586367501L;
|
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;
|
this.height = height;
|
||||||
onChanged = changedCallback;
|
onChanged = changedCallback;
|
||||||
|
|
||||||
text = new TextBuffer[this.height];
|
text = new TextBuffer[height];
|
||||||
textColour = new TextBuffer[this.height];
|
textColour = new TextBuffer[height];
|
||||||
backgroundColour = new TextBuffer[this.height];
|
backgroundColour = new TextBuffer[height];
|
||||||
for( int i = 0; i < this.height; i++ )
|
for( int i = 0; i < this.height; i++ )
|
||||||
{
|
{
|
||||||
text[i] = new TextBuffer( ' ', this.width );
|
text[i] = new TextBuffer( ' ', this.width );
|
||||||
@ -93,9 +93,9 @@ public class Terminal
|
|||||||
this.width = width;
|
this.width = width;
|
||||||
this.height = height;
|
this.height = height;
|
||||||
|
|
||||||
text = new TextBuffer[this.height];
|
text = new TextBuffer[height];
|
||||||
textColour = new TextBuffer[this.height];
|
textColour = new TextBuffer[height];
|
||||||
backgroundColour = new TextBuffer[this.height];
|
backgroundColour = new TextBuffer[height];
|
||||||
for( int i = 0; i < this.height; i++ )
|
for( int i = 0; i < this.height; i++ )
|
||||||
{
|
{
|
||||||
if( i >= oldHeight )
|
if( i >= oldHeight )
|
||||||
|
@ -12,7 +12,7 @@ public class TextBuffer
|
|||||||
public TextBuffer( char c, int length )
|
public TextBuffer( char c, int length )
|
||||||
{
|
{
|
||||||
text = new char[length];
|
text = new char[length];
|
||||||
this.fill( c );
|
fill( c );
|
||||||
}
|
}
|
||||||
|
|
||||||
public TextBuffer( String text )
|
public TextBuffer( String text )
|
||||||
@ -79,6 +79,7 @@ public class TextBuffer
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
public String toString()
|
public String toString()
|
||||||
{
|
{
|
||||||
return new String( text );
|
return new String( text );
|
||||||
|
@ -6,11 +6,11 @@
|
|||||||
package dan200.computercraft.data;
|
package dan200.computercraft.data;
|
||||||
|
|
||||||
import dan200.computercraft.ComputerCraft;
|
import dan200.computercraft.ComputerCraft;
|
||||||
|
import dan200.computercraft.shared.CommonHooks;
|
||||||
import dan200.computercraft.shared.Registry;
|
import dan200.computercraft.shared.Registry;
|
||||||
import dan200.computercraft.shared.data.BlockNamedEntityLootCondition;
|
import dan200.computercraft.shared.data.BlockNamedEntityLootCondition;
|
||||||
import dan200.computercraft.shared.data.HasComputerIdLootCondition;
|
import dan200.computercraft.shared.data.HasComputerIdLootCondition;
|
||||||
import dan200.computercraft.shared.data.PlayerCreativeLootCondition;
|
import dan200.computercraft.shared.data.PlayerCreativeLootCondition;
|
||||||
import dan200.computercraft.shared.CommonHooks;
|
|
||||||
import net.minecraft.block.Block;
|
import net.minecraft.block.Block;
|
||||||
import net.minecraft.data.DataGenerator;
|
import net.minecraft.data.DataGenerator;
|
||||||
import net.minecraft.loot.*;
|
import net.minecraft.loot.*;
|
||||||
|
@ -33,9 +33,9 @@ public final class TurtleUpgrades
|
|||||||
Wrapper( ITurtleUpgrade upgrade )
|
Wrapper( ITurtleUpgrade upgrade )
|
||||||
{
|
{
|
||||||
this.upgrade = upgrade;
|
this.upgrade = upgrade;
|
||||||
this.id = upgrade.getUpgradeID().toString();
|
id = upgrade.getUpgradeID().toString();
|
||||||
this.modId = ModLoadingContext.get().getActiveNamespace();
|
modId = ModLoadingContext.get().getActiveNamespace();
|
||||||
this.enabled = true;
|
enabled = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -61,7 +61,7 @@ public class ContainerHeldItem extends Container
|
|||||||
public Factory( ContainerType<ContainerHeldItem> type, ItemStack stack, Hand hand )
|
public Factory( ContainerType<ContainerHeldItem> type, ItemStack stack, Hand hand )
|
||||||
{
|
{
|
||||||
this.type = type;
|
this.type = type;
|
||||||
this.name = stack.getHoverName();
|
name = stack.getHoverName();
|
||||||
this.hand = hand;
|
this.hand = hand;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -25,14 +25,14 @@ public class ContainerViewComputer extends ContainerComputerBase implements ICon
|
|||||||
public ContainerViewComputer( int id, ServerComputer computer )
|
public ContainerViewComputer( int id, ServerComputer computer )
|
||||||
{
|
{
|
||||||
super( Registry.ModContainers.VIEW_COMPUTER.get(), id, player -> canInteractWith( computer, player ), computer, computer.getFamily() );
|
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 )
|
public ContainerViewComputer( int id, PlayerInventory player, ViewComputerContainerData data )
|
||||||
{
|
{
|
||||||
super( Registry.ModContainers.VIEW_COMPUTER.get(), id, player, data );
|
super( Registry.ModContainers.VIEW_COMPUTER.get(), id, player, data );
|
||||||
this.width = data.getWidth();
|
width = data.getWidth();
|
||||||
this.height = data.getHeight();
|
height = data.getHeight();
|
||||||
}
|
}
|
||||||
|
|
||||||
private static boolean canInteractWith( @Nonnull ServerComputer computer, @Nonnull PlayerEntity player )
|
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 );
|
return String.format( "Removing turtle upgrade %s.", id );
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
public boolean validate( ILogger logger )
|
public boolean validate( ILogger logger )
|
||||||
{
|
{
|
||||||
TrackingLogger trackLog = new TrackingLogger( logger );
|
TrackingLogger trackLog = new TrackingLogger( logger );
|
||||||
|
@ -41,7 +41,7 @@ public class RemoveTurtleUpgradeByItem implements IUndoableAction
|
|||||||
@Override
|
@Override
|
||||||
public void undo()
|
public void undo()
|
||||||
{
|
{
|
||||||
if( this.upgrade != null ) TurtleUpgrades.enable( upgrade );
|
if( upgrade != null ) TurtleUpgrades.enable( upgrade );
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -40,7 +40,7 @@ public class RemoveTurtleUpgradeByName implements IUndoableAction
|
|||||||
@Override
|
@Override
|
||||||
public void undo()
|
public void undo()
|
||||||
{
|
{
|
||||||
if( this.upgrade != null ) TurtleUpgrades.enable( upgrade );
|
if( upgrade != null ) TurtleUpgrades.enable( upgrade );
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -339,17 +339,17 @@ class RecipeResolver implements IRecipeManagerPlugin
|
|||||||
UpgradeInfo( ItemStack stack, ITurtleUpgrade turtle )
|
UpgradeInfo( ItemStack stack, ITurtleUpgrade turtle )
|
||||||
{
|
{
|
||||||
this.stack = stack;
|
this.stack = stack;
|
||||||
this.ingredient = of( stack );
|
ingredient = of( stack );
|
||||||
this.upgrade = this.turtle = turtle;
|
upgrade = this.turtle = turtle;
|
||||||
this.pocket = null;
|
pocket = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
UpgradeInfo( ItemStack stack, IPocketUpgrade pocket )
|
UpgradeInfo( ItemStack stack, IPocketUpgrade pocket )
|
||||||
{
|
{
|
||||||
this.stack = stack;
|
this.stack = stack;
|
||||||
this.ingredient = of( stack );
|
ingredient = of( stack );
|
||||||
this.turtle = null;
|
turtle = null;
|
||||||
this.upgrade = this.pocket = pocket;
|
upgrade = this.pocket = pocket;
|
||||||
}
|
}
|
||||||
|
|
||||||
List<Shaped> getRecipes()
|
List<Shaped> getRecipes()
|
||||||
|
@ -56,6 +56,9 @@ public final class NetworkHandler
|
|||||||
registerMainThread( 13, NetworkDirection.PLAY_TO_CLIENT, ComputerTerminalClientMessage::new );
|
registerMainThread( 13, NetworkDirection.PLAY_TO_CLIENT, ComputerTerminalClientMessage::new );
|
||||||
registerMainThread( 14, NetworkDirection.PLAY_TO_CLIENT, PlayRecordClientMessage.class, PlayRecordClientMessage::new );
|
registerMainThread( 14, NetworkDirection.PLAY_TO_CLIENT, PlayRecordClientMessage.class, PlayRecordClientMessage::new );
|
||||||
registerMainThread( 15, NetworkDirection.PLAY_TO_CLIENT, MonitorClientMessage.class, MonitorClientMessage::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 )
|
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 )
|
if( terminal == null )
|
||||||
{
|
{
|
||||||
this.width = this.height = 0;
|
width = height = 0;
|
||||||
this.buffer = null;
|
buffer = null;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
this.width = terminal.getWidth();
|
width = terminal.getWidth();
|
||||||
this.height = terminal.getHeight();
|
height = terminal.getHeight();
|
||||||
|
|
||||||
ByteBuf buf = this.buffer = Unpooled.buffer();
|
ByteBuf buf = buffer = Unpooled.buffer();
|
||||||
terminal.write( new PacketBuffer( buf ) );
|
terminal.write( new PacketBuffer( buf ) );
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public TerminalState( PacketBuffer buf )
|
public TerminalState( PacketBuffer buf )
|
||||||
{
|
{
|
||||||
this.colour = buf.readBoolean();
|
colour = buf.readBoolean();
|
||||||
this.compress = buf.readBoolean();
|
compress = buf.readBoolean();
|
||||||
|
|
||||||
if( buf.readBoolean() )
|
if( buf.readBoolean() )
|
||||||
{
|
{
|
||||||
this.width = buf.readVarInt();
|
width = buf.readVarInt();
|
||||||
this.height = buf.readVarInt();
|
height = buf.readVarInt();
|
||||||
|
|
||||||
int length = buf.readVarInt();
|
int length = buf.readVarInt();
|
||||||
this.buffer = readCompressed( buf, length, compress );
|
buffer = readCompressed( buf, length, compress );
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
this.width = this.height = 0;
|
width = height = 0;
|
||||||
this.buffer = null;
|
buffer = null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -16,14 +16,14 @@ public class ComputerContainerData implements ContainerData
|
|||||||
|
|
||||||
public ComputerContainerData( ServerComputer computer )
|
public ComputerContainerData( ServerComputer computer )
|
||||||
{
|
{
|
||||||
this.id = computer.getInstanceID();
|
id = computer.getInstanceID();
|
||||||
this.family = computer.getFamily();
|
family = computer.getFamily();
|
||||||
}
|
}
|
||||||
|
|
||||||
public ComputerContainerData( PacketBuffer buf )
|
public ComputerContainerData( PacketBuffer buf )
|
||||||
{
|
{
|
||||||
this.id = buf.readInt();
|
id = buf.readInt();
|
||||||
this.family = buf.readEnum( ComputerFamily.class );
|
family = buf.readEnum( ComputerFamily.class );
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -24,7 +24,7 @@ final class SaturatedMethod
|
|||||||
SaturatedMethod( Object target, NamedMethod<PeripheralMethod> method )
|
SaturatedMethod( Object target, NamedMethod<PeripheralMethod> method )
|
||||||
{
|
{
|
||||||
this.target = target;
|
this.target = target;
|
||||||
this.name = method.getName();
|
name = method.getName();
|
||||||
this.method = method.getMethod();
|
this.method = method.getMethod();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -53,7 +53,7 @@ public class TileCable extends TileGeneric
|
|||||||
@Override
|
@Override
|
||||||
public World getWorld()
|
public World getWorld()
|
||||||
{
|
{
|
||||||
return TileCable.this.getLevel();
|
return getLevel();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Nonnull
|
@Nonnull
|
||||||
|
@ -10,17 +10,23 @@ import dan200.computercraft.api.lua.ILuaContext;
|
|||||||
import dan200.computercraft.api.lua.LuaException;
|
import dan200.computercraft.api.lua.LuaException;
|
||||||
import dan200.computercraft.api.lua.LuaFunction;
|
import dan200.computercraft.api.lua.LuaFunction;
|
||||||
import dan200.computercraft.api.peripheral.IPeripheral;
|
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.network.play.server.SPlaySoundPacket;
|
||||||
import net.minecraft.server.MinecraftServer;
|
import net.minecraft.server.MinecraftServer;
|
||||||
import net.minecraft.state.properties.NoteBlockInstrument;
|
import net.minecraft.state.properties.NoteBlockInstrument;
|
||||||
import net.minecraft.util.ResourceLocation;
|
import net.minecraft.util.ResourceLocation;
|
||||||
import net.minecraft.util.ResourceLocationException;
|
import net.minecraft.util.ResourceLocationException;
|
||||||
import net.minecraft.util.SoundCategory;
|
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.util.math.vector.Vector3d;
|
||||||
import net.minecraft.world.World;
|
import net.minecraft.world.World;
|
||||||
|
|
||||||
import javax.annotation.Nonnull;
|
import javax.annotation.Nonnull;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
|
import java.util.UUID;
|
||||||
import java.util.concurrent.atomic.AtomicInteger;
|
import java.util.concurrent.atomic.AtomicInteger;
|
||||||
|
|
||||||
import static dan200.computercraft.api.lua.LuaValues.checkFinite;
|
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
|
public abstract class SpeakerPeripheral implements IPeripheral
|
||||||
{
|
{
|
||||||
|
private static final int MIN_TICKS_BETWEEN_SOUNDS = 1;
|
||||||
|
|
||||||
private long clock = 0;
|
private long clock = 0;
|
||||||
private long lastPlayTime = 0;
|
private long lastPlayTime = 0;
|
||||||
private final AtomicInteger notesThisTick = new AtomicInteger();
|
private final AtomicInteger notesThisTick = new AtomicInteger();
|
||||||
|
|
||||||
|
private long lastPositionTime;
|
||||||
|
private Vector3d lastPosition;
|
||||||
|
|
||||||
public void update()
|
public void update()
|
||||||
{
|
{
|
||||||
clock++;
|
clock++;
|
||||||
notesThisTick.set( 0 );
|
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 World getWorld();
|
||||||
|
|
||||||
public abstract Vector3d getPosition();
|
public abstract Vector3d getPosition();
|
||||||
|
|
||||||
|
protected abstract UUID getSource();
|
||||||
|
|
||||||
public boolean madeSound( long ticks )
|
public boolean madeSound( long ticks )
|
||||||
{
|
{
|
||||||
return clock - lastPlayTime <= 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
|
private synchronized boolean playSound( ILuaContext context, ResourceLocation name, float volume, float pitch, boolean isNote ) throws LuaException
|
||||||
{
|
{
|
||||||
if( clock - lastPlayTime < TileSpeaker.MIN_TICKS_BETWEEN_SOUNDS &&
|
if( clock - lastPlayTime < MIN_TICKS_BETWEEN_SOUNDS )
|
||||||
(!isNote || clock - lastPlayTime != 0 || notesThisTick.get() >= ComputerCraft.maxNotesPerTick) )
|
|
||||||
{
|
{
|
||||||
// Rate limiting occurs when we've already played a sound within the last tick, or we've
|
// Rate limiting occurs when we've already played a sound within the last tick.
|
||||||
// played more notes than allowable within the current tick.
|
if( !isNote ) return false;
|
||||||
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();
|
World world = getWorld();
|
||||||
Vector3d pos = getPosition();
|
Vector3d pos = getPosition();
|
||||||
|
|
||||||
|
float range = MathHelper.clamp( volume, 1.0f, 3.0f ) * 16;
|
||||||
|
|
||||||
context.issueMainThreadTask( () -> {
|
context.issueMainThreadTask( () -> {
|
||||||
MinecraftServer server = world.getServer();
|
MinecraftServer server = world.getServer();
|
||||||
if( server == null ) return null;
|
if( server == null ) return null;
|
||||||
|
|
||||||
float adjVolume = Math.min( volume, 3.0f );
|
if( isNote )
|
||||||
|
{
|
||||||
server.getPlayerList().broadcast(
|
server.getPlayerList().broadcast(
|
||||||
null, pos.x, pos.y, pos.z, adjVolume > 1.0f ? 16 * adjVolume : 16.0, world.dimension(),
|
null, pos.x, pos.y, pos.z, range, world.dimension(),
|
||||||
new SPlaySoundPacket( name, SoundCategory.RECORDS, pos, adjVolume, pitch )
|
new SPlaySoundPacket( name, SoundCategory.RECORDS, pos, range, pitch )
|
||||||
);
|
);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
NetworkHandler.sendToAllAround(
|
||||||
|
new SpeakerPlayClientMessage( getSource(), pos, name, range, pitch ),
|
||||||
|
world, pos, range
|
||||||
|
);
|
||||||
|
}
|
||||||
return null;
|
return null;
|
||||||
} );
|
} );
|
||||||
|
|
||||||
|
@ -7,6 +7,8 @@ package dan200.computercraft.shared.peripheral.speaker;
|
|||||||
|
|
||||||
import dan200.computercraft.api.peripheral.IPeripheral;
|
import dan200.computercraft.api.peripheral.IPeripheral;
|
||||||
import dan200.computercraft.shared.common.TileGeneric;
|
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 dan200.computercraft.shared.util.CapabilityUtil;
|
||||||
import net.minecraft.tileentity.ITickableTileEntity;
|
import net.minecraft.tileentity.ITickableTileEntity;
|
||||||
import net.minecraft.tileentity.TileEntityType;
|
import net.minecraft.tileentity.TileEntityType;
|
||||||
@ -19,15 +21,15 @@ import net.minecraftforge.common.util.LazyOptional;
|
|||||||
|
|
||||||
import javax.annotation.Nonnull;
|
import javax.annotation.Nonnull;
|
||||||
import javax.annotation.Nullable;
|
import javax.annotation.Nullable;
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
import static dan200.computercraft.shared.Capabilities.CAPABILITY_PERIPHERAL;
|
import static dan200.computercraft.shared.Capabilities.CAPABILITY_PERIPHERAL;
|
||||||
|
|
||||||
public class TileSpeaker extends TileGeneric implements ITickableTileEntity
|
public class TileSpeaker extends TileGeneric implements ITickableTileEntity
|
||||||
{
|
{
|
||||||
public static final int MIN_TICKS_BETWEEN_SOUNDS = 1;
|
|
||||||
|
|
||||||
private final SpeakerPeripheral peripheral;
|
private final SpeakerPeripheral peripheral;
|
||||||
private LazyOptional<IPeripheral> peripheralCap;
|
private LazyOptional<IPeripheral> peripheralCap;
|
||||||
|
private final UUID source = UUID.randomUUID();
|
||||||
|
|
||||||
public TileSpeaker( TileEntityType<TileSpeaker> type )
|
public TileSpeaker( TileEntityType<TileSpeaker> type )
|
||||||
{
|
{
|
||||||
@ -41,6 +43,13 @@ public class TileSpeaker extends TileGeneric implements ITickableTileEntity
|
|||||||
peripheral.update();
|
peripheral.update();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setRemoved()
|
||||||
|
{
|
||||||
|
super.setRemoved();
|
||||||
|
NetworkHandler.sendToAllPlayers( new SpeakerStopClientMessage( source ) );
|
||||||
|
}
|
||||||
|
|
||||||
@Nonnull
|
@Nonnull
|
||||||
@Override
|
@Override
|
||||||
public <T> LazyOptional<T> getCapability( @Nonnull Capability<T> cap, @Nullable Direction side )
|
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() );
|
return new Vector3d( pos.getX(), pos.getY(), pos.getZ() );
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected UUID getSource()
|
||||||
|
{
|
||||||
|
return speaker.source;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean equals( @Nullable IPeripheral other )
|
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 )
|
public Factory( ServerComputer computer, ItemStack stack, ItemPocketComputer item, Hand hand )
|
||||||
{
|
{
|
||||||
this.computer = computer;
|
this.computer = computer;
|
||||||
this.name = stack.getHoverName();
|
name = stack.getHoverName();
|
||||||
this.item = item;
|
this.item = item;
|
||||||
this.hand = hand;
|
this.hand = hand;
|
||||||
}
|
}
|
||||||
|
@ -6,11 +6,11 @@
|
|||||||
package dan200.computercraft.shared.pocket.peripherals;
|
package dan200.computercraft.shared.pocket.peripherals;
|
||||||
|
|
||||||
import dan200.computercraft.api.peripheral.IPeripheral;
|
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.util.math.vector.Vector3d;
|
||||||
import net.minecraft.world.World;
|
import net.minecraft.world.World;
|
||||||
|
|
||||||
public class PocketSpeakerPeripheral extends SpeakerPeripheral
|
public class PocketSpeakerPeripheral extends UpgradeSpeakerPeripheral
|
||||||
{
|
{
|
||||||
private World world = null;
|
private World world = null;
|
||||||
private Vector3d position = Vector3d.ZERO;
|
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.
|
* more information about the item at the cost of taking longer to run.
|
||||||
* @return The command result.
|
* @return The command result.
|
||||||
* @throws LuaException If the slot is out of range.
|
* @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.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.
|
* @cc.usage Print the current slot, assuming it contains 13 dirt.
|
||||||
*
|
*
|
||||||
@ -726,6 +725,7 @@ public class TurtleAPI implements ILuaAPI
|
|||||||
* -- count = 13,
|
* -- count = 13,
|
||||||
* -- }
|
* -- }
|
||||||
* }</pre>
|
* }</pre>
|
||||||
|
* @see InventoryMethods#getItemDetail Describes the information returned by a detailed query.
|
||||||
*/
|
*/
|
||||||
@LuaFunction
|
@LuaFunction
|
||||||
public final MethodResult getItemDetail( ILuaContext context, Optional<Integer> slot, Optional<Boolean> detailed ) throws LuaException
|
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 );
|
setFuelLevel( getFuelLevel() + addition );
|
||||||
}
|
}
|
||||||
|
|
||||||
private int issueCommand( ITurtleCommand command )
|
|
||||||
{
|
|
||||||
commandQueue.offer( new TurtleCommandQueueEntry( ++commandsIssued, command ) );
|
|
||||||
return commandsIssued;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Nonnull
|
@Nonnull
|
||||||
@Override
|
@Override
|
||||||
public MethodResult executeCommand( @Nonnull ITurtleCommand command )
|
public MethodResult executeCommand( @Nonnull ITurtleCommand command )
|
||||||
{
|
{
|
||||||
if( getWorld().isClientSide ) throw new UnsupportedOperationException( "Cannot run commands on the client" );
|
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
|
commandQueue.offer( new TurtleCommandQueueEntry( ++commandsIssued, command ) );
|
||||||
int commandID = issueCommand( command );
|
int commandID = commandsIssued;
|
||||||
return new CommandCallback( commandID ).pull;
|
return new CommandCallback( commandID ).pull;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -5,6 +5,7 @@
|
|||||||
*/
|
*/
|
||||||
package dan200.computercraft.shared.turtle.inventory;
|
package dan200.computercraft.shared.turtle.inventory;
|
||||||
|
|
||||||
|
import dan200.computercraft.client.gui.widgets.ComputerSidebar;
|
||||||
import dan200.computercraft.shared.Registry;
|
import dan200.computercraft.shared.Registry;
|
||||||
import dan200.computercraft.shared.computer.core.ComputerFamily;
|
import dan200.computercraft.shared.computer.core.ComputerFamily;
|
||||||
import dan200.computercraft.shared.computer.core.IComputer;
|
import dan200.computercraft.shared.computer.core.IComputer;
|
||||||
@ -27,8 +28,10 @@ import java.util.function.Predicate;
|
|||||||
|
|
||||||
public class ContainerTurtle extends ContainerComputerBase
|
public class ContainerTurtle extends ContainerComputerBase
|
||||||
{
|
{
|
||||||
|
public static final int BORDER = 8;
|
||||||
public static final int PLAYER_START_Y = 134;
|
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;
|
private final IIntArray properties;
|
||||||
|
|
||||||
@ -56,14 +59,14 @@ public class ContainerTurtle extends ContainerComputerBase
|
|||||||
{
|
{
|
||||||
for( int x = 0; x < 9; x++ )
|
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
|
// Player hotbar
|
||||||
for( int x = 0; x < 9; x++ )
|
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.TurtleSide;
|
||||||
import dan200.computercraft.api.turtle.TurtleUpgradeType;
|
import dan200.computercraft.api.turtle.TurtleUpgradeType;
|
||||||
import dan200.computercraft.shared.Registry;
|
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.client.renderer.model.ModelResourceLocation;
|
||||||
import net.minecraft.util.ResourceLocation;
|
import net.minecraft.util.ResourceLocation;
|
||||||
import net.minecraft.util.math.BlockPos;
|
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 leftModel = new ModelResourceLocation( "computercraft:turtle_speaker_upgrade_left", "inventory" );
|
||||||
private static final ModelResourceLocation rightModel = new ModelResourceLocation( "computercraft:turtle_speaker_upgrade_right", "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;
|
ITurtleAccess turtle;
|
||||||
|
|
||||||
|
@ -65,7 +65,7 @@ public class TurtleTool extends AbstractTurtleUpgrade
|
|||||||
public TurtleTool( ResourceLocation id, ItemStack craftItem, ItemStack toolItem )
|
public TurtleTool( ResourceLocation id, ItemStack craftItem, ItemStack toolItem )
|
||||||
{
|
{
|
||||||
super( id, TurtleUpgradeType.TOOL, craftItem );
|
super( id, TurtleUpgradeType.TOOL, craftItem );
|
||||||
this.item = toolItem;
|
item = toolItem;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -318,7 +318,7 @@ public class FakeNetHandler extends ServerPlayNetHandler
|
|||||||
@Override
|
@Override
|
||||||
public void disconnect( @Nonnull ITextComponent message )
|
public void disconnect( @Nonnull ITextComponent message )
|
||||||
{
|
{
|
||||||
this.closeReason = message;
|
closeReason = message;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Nonnull
|
@Nonnull
|
||||||
|
@ -110,5 +110,11 @@
|
|||||||
"tracking_field.computercraft.coroutines_dead.name": "Coroutines disposed",
|
"tracking_field.computercraft.coroutines_dead.name": "Coroutines disposed",
|
||||||
"gui.computercraft.tooltip.copy": "Copy to clipboard",
|
"gui.computercraft.tooltip.copy": "Copy to clipboard",
|
||||||
"gui.computercraft.tooltip.computer_id": "Computer ID: %s",
|
"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
|
sPath = _sPath
|
||||||
end
|
end
|
||||||
|
|
||||||
|
local extensions = { "", ".md", ".txt" }
|
||||||
|
|
||||||
--- Returns the location of the help file for the given topic.
|
--- Returns the location of the help file for the given topic.
|
||||||
--
|
--
|
||||||
-- @tparam string topic The topic to find
|
-- @tparam string topic The topic to find
|
||||||
-- @treturn string|nil The path to the given topic's help file, or `nil` if it
|
-- @treturn string|nil The path to the given topic's help file, or `nil` if it
|
||||||
-- cannot be found.
|
-- cannot be found.
|
||||||
-- @usage help.lookup("disk")
|
-- @usage help.lookup("disk")
|
||||||
function lookup(_sTopic)
|
function lookup(topic)
|
||||||
expect(1, _sTopic, "string")
|
expect(1, topic, "string")
|
||||||
-- Look on the path variable
|
-- Look on the path variable
|
||||||
for sPath in string.gmatch(sPath, "[^:]+") do
|
for path in string.gmatch(sPath, "[^:]+") do
|
||||||
sPath = fs.combine(sPath, _sTopic)
|
path = fs.combine(path, topic)
|
||||||
if fs.exists(sPath) and not fs.isDir(sPath) then
|
for _, extension in ipairs(extensions) do
|
||||||
return sPath
|
local file = path .. extension
|
||||||
elseif fs.exists(sPath .. ".txt") and not fs.isDir(sPath .. ".txt") then
|
if fs.exists(file) and not fs.isDir(file) then
|
||||||
return sPath .. ".txt"
|
return file
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
@ -66,8 +69,11 @@ function topics()
|
|||||||
for _, sFile in pairs(tList) do
|
for _, sFile in pairs(tList) do
|
||||||
if string.sub(sFile, 1, 1) ~= "." then
|
if string.sub(sFile, 1, 1) ~= "." then
|
||||||
if not fs.isDir(fs.combine(sPath, sFile)) then
|
if not fs.isDir(fs.combine(sPath, sFile)) then
|
||||||
if #sFile > 4 and sFile:sub(-4) == ".txt" then
|
for i = 2, #extensions do
|
||||||
sFile = sFile:sub(1, -5)
|
local extension = extensions[i]
|
||||||
|
if #sFile > #extension and sFile:sub(-#extension) == extension then
|
||||||
|
sFile = sFile:sub(1, -#extension - 1)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
tItems[sFile] = true
|
tItems[sFile] = true
|
||||||
end
|
end
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
craft is a program for Crafty Turtles. Craft will craft a stack of items using the current inventory.
|
craft is a program for Crafty Turtles. Craft will craft a stack of items using the current inventory.
|
||||||
|
|
||||||
ex:
|
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
|
"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.
|
--- Complete the name of a file relative to the current working directory.
|
||||||
--
|
--
|
||||||
-- @tparam table shell The shell we're completing in
|
-- @tparam table shell The shell we're completing in.
|
||||||
-- @tparam { string... } choices The list of choices to complete from.
|
-- @tparam string text Current text to complete.
|
||||||
-- @treturn { string... } A list of suffixes of matching files.
|
-- @treturn { string... } A list of suffixes of matching files.
|
||||||
local function file(shell, text)
|
local function file(shell, text)
|
||||||
return fs.complete(text, shell.dir(), true, false)
|
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.
|
--- Complete the name of a directory relative to the current working directory.
|
||||||
--
|
--
|
||||||
-- @tparam table shell The shell we're completing in
|
-- @tparam table shell The shell we're completing in.
|
||||||
-- @tparam { string... } choices The list of choices to complete from.
|
-- @tparam string text Current text to complete.
|
||||||
-- @treturn { string... } A list of suffixes of matching directories.
|
-- @treturn { string... } A list of suffixes of matching directories.
|
||||||
local function dir(shell, text)
|
local function dir(shell, text)
|
||||||
return fs.complete(text, shell.dir(), false, true)
|
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
|
--- Complete the name of a file or directory relative to the current working
|
||||||
-- directory.
|
-- directory.
|
||||||
--
|
--
|
||||||
-- @tparam table shell The shell we're completing in
|
-- @tparam table shell The shell we're completing in.
|
||||||
-- @tparam { string... } choices The list of choices to complete from.
|
-- @tparam string text Current text to complete.
|
||||||
-- @tparam { string... } previous The shell arguments before this one.
|
-- @tparam { string... } previous The shell arguments before this one.
|
||||||
-- @tparam[opt] boolean add_space Whether to add a space after the completed item.
|
-- @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.
|
-- @treturn { string... } A list of suffixes of matching files and directories.
|
||||||
@ -74,14 +74,46 @@ end
|
|||||||
|
|
||||||
--- Complete the name of a program.
|
--- Complete the name of a program.
|
||||||
--
|
--
|
||||||
-- @tparam table shell The shell we're completing in
|
-- @tparam table shell The shell we're completing in.
|
||||||
-- @tparam { string... } choices The list of choices to complete from.
|
-- @tparam string text Current text to complete.
|
||||||
-- @treturn { string... } A list of suffixes of matching programs.
|
-- @treturn { string... } A list of suffixes of matching programs.
|
||||||
-- @see shell.completeProgram
|
-- @see shell.completeProgram
|
||||||
local function program(shell, text)
|
local function program(shell, text)
|
||||||
return shell.completeProgram(text)
|
return shell.completeProgram(text)
|
||||||
end
|
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.
|
--[[- A helper function for building shell completion arguments.
|
||||||
|
|
||||||
This accepts a series of single-argument completion functions, and combines
|
This accepts a series of single-argument completion functions, and combines
|
||||||
@ -144,6 +176,7 @@ return {
|
|||||||
dir = dir,
|
dir = dir,
|
||||||
dirOrFile = dirOrFile,
|
dirOrFile = dirOrFile,
|
||||||
program = program,
|
program = program,
|
||||||
|
programWithArgs = programWithArgs,
|
||||||
|
|
||||||
-- Re-export various other functions
|
-- Re-export various other functions
|
||||||
help = wrap(help.completeTopic), --- Wraps @{help.completeTopic} as a @{build} compatible function.
|
help = wrap(help.completeTopic), --- Wraps @{help.completeTopic} as a @{build} compatible function.
|
||||||
|
@ -14,16 +14,127 @@ if sTopic == "index" then
|
|||||||
end
|
end
|
||||||
|
|
||||||
local strings = require "cc.strings"
|
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
|
-- Normalise the strings suitable for use with blit. We could skip this and
|
||||||
-- just use term.write, but saves us a clearLine call.
|
-- just use term.write, but saves us a clearLine call.
|
||||||
for k, line in pairs(lines) do
|
for k, line in pairs(lines) do
|
||||||
lines[k] = strings.ensure_width(line, width)
|
lines[k] = strings.ensure_width(line, width)
|
||||||
|
fg[k] = fg_line
|
||||||
|
bg[k] = bg_line
|
||||||
end
|
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
|
end
|
||||||
|
|
||||||
local sFile = help.lookup(sTopic)
|
local sFile = help.lookup(sTopic)
|
||||||
@ -33,31 +144,40 @@ if not file then
|
|||||||
return
|
return
|
||||||
end
|
end
|
||||||
|
|
||||||
local contents = file:read("*a"):gsub("(\n *)[-*]( +)", "%1\7%2")
|
local contents = file:read("*a")
|
||||||
file:close()
|
file:close()
|
||||||
|
|
||||||
|
local word_wrap = sFile:sub(-3) == ".md" and word_wrap_markdown or word_wrap_basic
|
||||||
local width, height = term.getSize()
|
local width, height = term.getSize()
|
||||||
local lines = word_wrap(contents, width)
|
local lines, fg, bg, sections = word_wrap(contents, width)
|
||||||
local print_height = #lines
|
local print_height = #lines
|
||||||
|
|
||||||
-- If we fit within the screen, just display without pagination.
|
-- If we fit within the screen, just display without pagination.
|
||||||
if print_height <= height then
|
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
|
return
|
||||||
end
|
end
|
||||||
|
|
||||||
|
local current_section = nil
|
||||||
local offset = 0
|
local offset = 0
|
||||||
|
|
||||||
local function draw()
|
--- Find the currently visible seciton, or nil if this document has no sections.
|
||||||
local fg, bg = ("0"):rep(width), ("f"):rep(width)
|
--
|
||||||
for y = 1, height - 1 do
|
-- This could potentially be a binary search, but right now it's not worth it.
|
||||||
term.setCursorPos(1, y)
|
local function find_section()
|
||||||
if y + offset > print_height then
|
for i = #sections, 1, -1 do
|
||||||
-- Should only happen if we resize the terminal to a larger one
|
if sections[i].offset <= offset then
|
||||||
-- than actually needed for the current text.
|
return i
|
||||||
term.clearLine()
|
|
||||||
else
|
|
||||||
term.blit(lines[y + offset], fg, bg)
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@ -68,7 +188,10 @@ local function draw_menu()
|
|||||||
term.clearLine()
|
term.clearLine()
|
||||||
|
|
||||||
local tag = "Help: " .. sTopic
|
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
|
if width >= #tag + 16 then
|
||||||
term.setCursorPos(width - 14, height)
|
term.setCursorPos(width - 14, height)
|
||||||
@ -76,11 +199,31 @@ local function draw_menu()
|
|||||||
end
|
end
|
||||||
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()
|
||||||
draw_menu()
|
draw_menu()
|
||||||
|
|
||||||
while true do
|
while true do
|
||||||
local event, param = os.pullEvent()
|
local event, param = os.pullEventRaw()
|
||||||
if event == "key" then
|
if event == "key" then
|
||||||
if param == keys.up and offset > 0 then
|
if param == keys.up and offset > 0 then
|
||||||
offset = offset - 1
|
offset = offset - 1
|
||||||
@ -97,6 +240,12 @@ while true do
|
|||||||
elseif param == keys.home then
|
elseif param == keys.home then
|
||||||
offset = 0
|
offset = 0
|
||||||
draw()
|
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
|
elseif param == keys["end"] then
|
||||||
offset = print_height - height
|
offset = print_height - height
|
||||||
draw()
|
draw()
|
||||||
@ -124,6 +273,8 @@ while true do
|
|||||||
offset = math.max(math.min(offset, print_height - height), 0)
|
offset = math.max(math.min(offset, print_height - height), 0)
|
||||||
draw()
|
draw()
|
||||||
draw_menu()
|
draw_menu()
|
||||||
|
elseif event == "terminate" then
|
||||||
|
break
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -51,7 +51,7 @@ local function get(sUrl)
|
|||||||
|
|
||||||
local sResponse = response.readAll()
|
local sResponse = response.readAll()
|
||||||
response.close()
|
response.close()
|
||||||
return sResponse
|
return sResponse or ""
|
||||||
end
|
end
|
||||||
|
|
||||||
if run then
|
if run then
|
||||||
@ -79,7 +79,12 @@ else
|
|||||||
local res = get(url)
|
local res = get(url)
|
||||||
if not res then return end
|
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.write(res)
|
||||||
file.close()
|
file.close()
|
||||||
|
|
||||||
|
@ -7,7 +7,7 @@ local function printUsage()
|
|||||||
end
|
end
|
||||||
|
|
||||||
local tArgs = { ... }
|
local tArgs = { ... }
|
||||||
if #tArgs < 2 then
|
if #tArgs < 2 or tArgs[1] == "scale" and #tArgs < 3 then
|
||||||
printUsage()
|
printUsage()
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
@ -21,7 +21,7 @@ if tArgs[1] == "scale" then
|
|||||||
|
|
||||||
local nRes = tonumber(tArgs[3])
|
local nRes = tonumber(tArgs[3])
|
||||||
if nRes == nil or nRes < 0.5 or nRes > 5 then
|
if nRes == nil or nRes < 0.5 or nRes > 5 then
|
||||||
print("Invalid scale: " .. nRes)
|
print("Invalid scale: " .. tArgs[3])
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -9,20 +9,19 @@ if not turtle.craft then
|
|||||||
end
|
end
|
||||||
|
|
||||||
local tArgs = { ... }
|
local tArgs = { ... }
|
||||||
local nLimit = nil
|
local nLimit = tonumber(tArgs[1])
|
||||||
if #tArgs < 1 then
|
|
||||||
|
if not nLimit and tArgs[1] ~= "all" then
|
||||||
local programName = arg[0] or fs.getName(shell.getRunningProgram())
|
local programName = arg[0] or fs.getName(shell.getRunningProgram())
|
||||||
print("Usage: " .. programName .. " [number]")
|
print("Usage: " .. programName .. " all|<number>")
|
||||||
return
|
return
|
||||||
else
|
|
||||||
nLimit = tonumber(tArgs[1])
|
|
||||||
end
|
end
|
||||||
|
|
||||||
local nCrafted = 0
|
local nCrafted = 0
|
||||||
local nOldCount = turtle.getItemCount(turtle.getSelectedSlot())
|
local nOldCount = turtle.getItemCount(turtle.getSelectedSlot())
|
||||||
if turtle.craft(nLimit) then
|
if turtle.craft(nLimit) then
|
||||||
local nNewCount = turtle.getItemCount(turtle.getSelectedSlot())
|
local nNewCount = turtle.getItemCount(turtle.getSelectedSlot())
|
||||||
if nOldCount <= nLimit then
|
if not nLimit or nOldCount <= nLimit then
|
||||||
nCrafted = nNewCount
|
nCrafted = nNewCount
|
||||||
else
|
else
|
||||||
nCrafted = nOldCount - nNewCount
|
nCrafted = nOldCount - nNewCount
|
||||||
|
@ -81,9 +81,17 @@ shell.setCompletionFunction("rom/programs/monitor.lua", completion.build(
|
|||||||
if previous[2] == "scale" then
|
if previous[2] == "scale" then
|
||||||
return completion.peripheral(shell, text, previous, true)
|
return completion.peripheral(shell, text, previous, true)
|
||||||
else
|
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
|
||||||
|
end,
|
||||||
|
many = true,
|
||||||
|
}
|
||||||
))
|
))
|
||||||
|
|
||||||
shell.setCompletionFunction("rom/programs/move.lua", completion.build(
|
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, true },
|
||||||
completion.dirOrFile
|
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/type.lua", completion.build(completion.dirOrFile))
|
||||||
shell.setCompletionFunction("rom/programs/set.lua", completion.build({ completion.setting, true }))
|
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/bg.lua", completion.build({ completion.programWithArgs, 2, many = true }))
|
||||||
shell.setCompletionFunction("rom/programs/advanced/fg.lua", completion.build(completion.program))
|
shell.setCompletionFunction("rom/programs/advanced/fg.lua", completion.build({ completion.programWithArgs, 2, many = true }))
|
||||||
shell.setCompletionFunction("rom/programs/fun/dj.lua", completion.build(
|
shell.setCompletionFunction("rom/programs/fun/dj.lua", completion.build(
|
||||||
{ completion.choice, { "play", "play ", "stop " } },
|
{ completion.choice, { "play", "play ", "stop " } },
|
||||||
completion.peripheral
|
completion.peripheral
|
||||||
|
@ -32,7 +32,7 @@ public class AddressRuleTest
|
|||||||
@ParameterizedTest
|
@ParameterizedTest
|
||||||
@ValueSource( strings = {
|
@ValueSource( strings = {
|
||||||
"0.0.0.0", "[::]",
|
"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"
|
"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 )
|
public void blocksLocalDomains( String domain )
|
||||||
|
@ -18,5 +18,10 @@ describe("The help library", function()
|
|||||||
help.completeTopic("")
|
help.completeTopic("")
|
||||||
expect.error(help.completeTopic, nil):eq("bad argument #1 (expected string, got nil)")
|
expect.error(help.completeTopic, nil):eq("bad argument #1 (expected string, got nil)")
|
||||||
end)
|
end)
|
||||||
|
|
||||||
|
it("completes topics without extensions", function()
|
||||||
|
expect(help.completeTopic("changel")):same { "og" }
|
||||||
|
expect(help.completeTopic("turt")):same { "le" }
|
||||||
|
end)
|
||||||
end)
|
end)
|
||||||
end)
|
end)
|
||||||
|
@ -17,6 +17,30 @@ describe("cc.shell.completion", function()
|
|||||||
end)
|
end)
|
||||||
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()
|
describe("build", function()
|
||||||
it("completes multiple arguments", function()
|
it("completes multiple arguments", function()
|
||||||
local spec = c.build(
|
local spec = c.build(
|
||||||
|
@ -1,7 +1,8 @@
|
|||||||
local capture = require "test_helpers".capture_program
|
local capture = require "test_helpers".capture_program
|
||||||
|
|
||||||
describe("The wget program", function()
|
describe("The wget program", function()
|
||||||
local function setup_request()
|
local default_contents = [[print("Hello", ...)]]
|
||||||
|
local function setup_request(contents)
|
||||||
stub(_G, "http", {
|
stub(_G, "http", {
|
||||||
checkURL = function()
|
checkURL = function()
|
||||||
return true
|
return true
|
||||||
@ -9,7 +10,7 @@ describe("The wget program", function()
|
|||||||
get = function()
|
get = function()
|
||||||
return {
|
return {
|
||||||
readAll = function()
|
readAll = function()
|
||||||
return [[print("Hello", ...)]]
|
return contents
|
||||||
end,
|
end,
|
||||||
close = function()
|
close = function()
|
||||||
end,
|
end,
|
||||||
@ -19,28 +20,52 @@ describe("The wget program", function()
|
|||||||
end
|
end
|
||||||
|
|
||||||
it("downloads one file", function()
|
it("downloads one file", function()
|
||||||
setup_request()
|
fs.delete("/example.com")
|
||||||
|
setup_request(default_contents)
|
||||||
|
|
||||||
capture(stub, "wget", "https://example.com")
|
capture(stub, "wget", "https://example.com")
|
||||||
|
|
||||||
expect(fs.exists("/example.com")):eq(true)
|
expect(fs.exists("/example.com")):eq(true)
|
||||||
end)
|
end)
|
||||||
|
|
||||||
it("downloads one file with given filename", function()
|
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")
|
capture(stub, "wget", "https://example.com /test-files/download")
|
||||||
|
|
||||||
expect(fs.exists("/test-files/download")):eq(true)
|
expect(fs.exists("/test-files/download")):eq(true)
|
||||||
end)
|
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()
|
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"))
|
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 = "" }
|
:matches { ok = true, output = "Connecting to http://test.com... Success.\nHello a b c\n", error = "" }
|
||||||
end)
|
end)
|
||||||
|
|
||||||
it("displays its usage when given no arguments", function()
|
it("displays its usage when given no arguments", function()
|
||||||
setup_request()
|
setup_request(default_contents)
|
||||||
|
|
||||||
expect(capture(stub, "wget"))
|
expect(capture(stub, "wget"))
|
||||||
:matches { ok = true, output = "Usage:\nwget <url> [filename]\nwget run <url>\n", error = "" }
|
: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 = "" }
|
:matches { ok = true, output = "", error = "" }
|
||||||
expect(r):equals(0.5)
|
expect(r):equals(0.5)
|
||||||
end)
|
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)
|
end)
|
||||||
|
@ -19,7 +19,14 @@ describe("The craft program", function()
|
|||||||
stub(_G, "turtle", { craft = function() end })
|
stub(_G, "turtle", { craft = function() end })
|
||||||
|
|
||||||
expect(capture(stub, "/rom/programs/turtle/craft.lua"))
|
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)
|
end)
|
||||||
|
|
||||||
it("crafts multiple items", function()
|
it("crafts multiple items", function()
|
||||||
@ -66,4 +73,17 @@ describe("The craft program", function()
|
|||||||
expect(capture(stub, "/rom/programs/turtle/craft.lua 1"))
|
expect(capture(stub, "/rom/programs/turtle/craft.lua 1"))
|
||||||
:matches { ok = true, output = "No items crafted\n", error = "" }
|
:matches { ok = true, output = "No items crafted\n", error = "" }
|
||||||
end)
|
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)
|
end)
|
||||||
|