Merge branch 'mc-1.20.x' into mc-1.21.x
1
.github/ISSUE_TEMPLATE/bug_report.yaml
vendored
@ -1,6 +1,7 @@
|
|||||||
name: Bug report
|
name: Bug report
|
||||||
description: Report some misbehaviour in the mod
|
description: Report some misbehaviour in the mod
|
||||||
labels: [ bug ]
|
labels: [ bug ]
|
||||||
|
type: bug
|
||||||
body:
|
body:
|
||||||
- type: dropdown
|
- type: dropdown
|
||||||
id: mc-version
|
id: mc-version
|
||||||
|
1
.github/ISSUE_TEMPLATE/feature_request.md
vendored
@ -2,6 +2,7 @@
|
|||||||
name: Feature request
|
name: Feature request
|
||||||
about: Suggest an idea or improvement
|
about: Suggest an idea or improvement
|
||||||
labels: enhancement
|
labels: enhancement
|
||||||
|
type: feature
|
||||||
---
|
---
|
||||||
|
|
||||||
<!--
|
<!--
|
||||||
|
@ -10,9 +10,7 @@ import org.gradle.api.GradleException
|
|||||||
import org.gradle.api.NamedDomainObjectProvider
|
import org.gradle.api.NamedDomainObjectProvider
|
||||||
import org.gradle.api.Project
|
import org.gradle.api.Project
|
||||||
import org.gradle.api.Task
|
import org.gradle.api.Task
|
||||||
import org.gradle.api.artifacts.Dependency
|
|
||||||
import org.gradle.api.plugins.JavaPluginExtension
|
import org.gradle.api.plugins.JavaPluginExtension
|
||||||
import org.gradle.api.provider.ListProperty
|
|
||||||
import org.gradle.api.provider.Provider
|
import org.gradle.api.provider.Provider
|
||||||
import org.gradle.api.provider.SetProperty
|
import org.gradle.api.provider.SetProperty
|
||||||
import org.gradle.api.tasks.SourceSet
|
import org.gradle.api.tasks.SourceSet
|
||||||
@ -33,6 +31,10 @@ import java.net.URI
|
|||||||
import java.util.regex.Pattern
|
import java.util.regex.Pattern
|
||||||
|
|
||||||
abstract class CCTweakedExtension(private val project: Project) {
|
abstract class CCTweakedExtension(private val project: Project) {
|
||||||
|
/** Get the hash of the latest git commit. */
|
||||||
|
val gitHash: Provider<String> =
|
||||||
|
gitProvider("<no git commit>", listOf("rev-parse", "HEAD")) { it.trim() }
|
||||||
|
|
||||||
/** Get the current git branch. */
|
/** Get the current git branch. */
|
||||||
val gitBranch: Provider<String> =
|
val gitBranch: Provider<String> =
|
||||||
gitProvider("<no git branch>", listOf("rev-parse", "--abbrev-ref", "HEAD")) { it.trim() }
|
gitProvider("<no git branch>", listOf("rev-parse", "--abbrev-ref", "HEAD")) { it.trim() }
|
||||||
@ -164,6 +166,7 @@ abstract class CCTweakedExtension(private val project: Project) {
|
|||||||
jacoco.applyTo(this)
|
jacoco.applyTo(this)
|
||||||
|
|
||||||
extensions.configure(JacocoTaskExtension::class.java) {
|
extensions.configure(JacocoTaskExtension::class.java) {
|
||||||
|
includes = listOf("dan200.computercraft.*")
|
||||||
excludes = listOf(
|
excludes = listOf(
|
||||||
"dan200.computercraft.mixin.*", // Exclude mixins, as they're not executed at runtime.
|
"dan200.computercraft.mixin.*", // Exclude mixins, as they're not executed at runtime.
|
||||||
"dan200.computercraft.shared.Capabilities$*", // Exclude capability tokens, as Forge rewrites them.
|
"dan200.computercraft.shared.Capabilities$*", // Exclude capability tokens, as Forge rewrites them.
|
||||||
|
@ -19,8 +19,8 @@ import dan200.computercraft.api.ComputerCraftAPI;
|
|||||||
*/
|
*/
|
||||||
public interface WiredElement extends WiredSender {
|
public interface WiredElement extends WiredSender {
|
||||||
/**
|
/**
|
||||||
* Called when objects on the network change. This may occur when network nodes are added or removed, or when
|
* Called when peripherals on the network change. This may occur when network nodes are added or removed, or when
|
||||||
* peripherals change.
|
* peripherals are attached or detached from a modem.
|
||||||
*
|
*
|
||||||
* @param change The change which occurred.
|
* @param change The change which occurred.
|
||||||
* @see WiredNetworkChange
|
* @see WiredNetworkChange
|
||||||
|
@ -10,10 +10,10 @@ import dan200.computercraft.shared.computer.menu.ComputerMenu;
|
|||||||
import dan200.computercraft.shared.network.server.ComputerActionServerMessage;
|
import dan200.computercraft.shared.network.server.ComputerActionServerMessage;
|
||||||
import dan200.computercraft.shared.network.server.KeyEventServerMessage;
|
import dan200.computercraft.shared.network.server.KeyEventServerMessage;
|
||||||
import dan200.computercraft.shared.network.server.MouseEventServerMessage;
|
import dan200.computercraft.shared.network.server.MouseEventServerMessage;
|
||||||
import dan200.computercraft.shared.network.server.QueueEventServerMessage;
|
import dan200.computercraft.shared.network.server.PasteEventComputerMessage;
|
||||||
import net.minecraft.world.inventory.AbstractContainerMenu;
|
import net.minecraft.world.inventory.AbstractContainerMenu;
|
||||||
|
|
||||||
import javax.annotation.Nullable;
|
import java.nio.ByteBuffer;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* An {@link InputHandler} for use on the client.
|
* An {@link InputHandler} for use on the client.
|
||||||
@ -27,6 +27,11 @@ public final class ClientInputHandler implements InputHandler {
|
|||||||
this.menu = menu;
|
this.menu = menu;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void terminate() {
|
||||||
|
ClientNetworking.sendToServer(new ComputerActionServerMessage(menu, ComputerActionServerMessage.Action.TERMINATE));
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void turnOn() {
|
public void turnOn() {
|
||||||
ClientNetworking.sendToServer(new ComputerActionServerMessage(menu, ComputerActionServerMessage.Action.TURN_ON));
|
ClientNetworking.sendToServer(new ComputerActionServerMessage(menu, ComputerActionServerMessage.Action.TURN_ON));
|
||||||
@ -42,11 +47,6 @@ public final class ClientInputHandler implements InputHandler {
|
|||||||
ClientNetworking.sendToServer(new ComputerActionServerMessage(menu, ComputerActionServerMessage.Action.REBOOT));
|
ClientNetworking.sendToServer(new ComputerActionServerMessage(menu, ComputerActionServerMessage.Action.REBOOT));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public void queueEvent(String event, @Nullable Object[] arguments) {
|
|
||||||
ClientNetworking.sendToServer(new QueueEventServerMessage(menu, event, arguments));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void keyDown(int key, boolean repeat) {
|
public void keyDown(int key, boolean repeat) {
|
||||||
ClientNetworking.sendToServer(new KeyEventServerMessage(menu, repeat ? KeyEventServerMessage.Action.REPEAT : KeyEventServerMessage.Action.DOWN, key));
|
ClientNetworking.sendToServer(new KeyEventServerMessage(menu, repeat ? KeyEventServerMessage.Action.REPEAT : KeyEventServerMessage.Action.DOWN, key));
|
||||||
@ -57,6 +57,16 @@ public final class ClientInputHandler implements InputHandler {
|
|||||||
ClientNetworking.sendToServer(new KeyEventServerMessage(menu, KeyEventServerMessage.Action.UP, key));
|
ClientNetworking.sendToServer(new KeyEventServerMessage(menu, KeyEventServerMessage.Action.UP, key));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void charTyped(byte chr) {
|
||||||
|
ClientNetworking.sendToServer(new KeyEventServerMessage(menu, KeyEventServerMessage.Action.CHAR, chr));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void paste(ByteBuffer contents) {
|
||||||
|
ClientNetworking.sendToServer(new PasteEventComputerMessage(menu, contents));
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void mouseClick(int button, int x, int y) {
|
public void mouseClick(int button, int x, int y) {
|
||||||
ClientNetworking.sendToServer(new MouseEventServerMessage(menu, MouseEventServerMessage.Action.CLICK, button, x, y));
|
ClientNetworking.sendToServer(new MouseEventServerMessage(menu, MouseEventServerMessage.Action.CLICK, button, x, y));
|
||||||
|
@ -34,8 +34,8 @@ public final class GuiSprites extends TextureAtlasHolder {
|
|||||||
|
|
||||||
private static ButtonTextures button(String name) {
|
private static ButtonTextures button(String name) {
|
||||||
return new ButtonTextures(
|
return new ButtonTextures(
|
||||||
ResourceLocation.fromNamespaceAndPath(ComputerCraftAPI.MOD_ID, "gui/buttons/" + name),
|
ResourceLocation.fromNamespaceAndPath(ComputerCraftAPI.MOD_ID, "buttons/" + name),
|
||||||
ResourceLocation.fromNamespaceAndPath(ComputerCraftAPI.MOD_ID, "gui/buttons/" + name + "_hover")
|
ResourceLocation.fromNamespaceAndPath(ComputerCraftAPI.MOD_ID, "buttons/" + name + "_hover")
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -96,12 +96,8 @@ public final class GuiSprites extends TextureAtlasHolder {
|
|||||||
* @param active The texture for the button when it is active (hovered or focused).
|
* @param active The texture for the button when it is active (hovered or focused).
|
||||||
*/
|
*/
|
||||||
public record ButtonTextures(ResourceLocation normal, ResourceLocation active) {
|
public record ButtonTextures(ResourceLocation normal, ResourceLocation active) {
|
||||||
public TextureAtlasSprite get(boolean active) {
|
public ResourceLocation get(boolean isActive) {
|
||||||
return GuiSprites.get(active ? this.active : normal);
|
return isActive ? active : normal;
|
||||||
}
|
|
||||||
|
|
||||||
public Stream<ResourceLocation> textures() {
|
|
||||||
return Stream.of(normal, active);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -26,6 +26,9 @@ public class TurtleScreen extends AbstractComputerScreen<TurtleMenu> {
|
|||||||
private static final ResourceLocation BACKGROUND_NORMAL = ResourceLocation.fromNamespaceAndPath(ComputerCraftAPI.MOD_ID, "textures/gui/turtle_normal.png");
|
private static final ResourceLocation BACKGROUND_NORMAL = ResourceLocation.fromNamespaceAndPath(ComputerCraftAPI.MOD_ID, "textures/gui/turtle_normal.png");
|
||||||
private static final ResourceLocation BACKGROUND_ADVANCED = ResourceLocation.fromNamespaceAndPath(ComputerCraftAPI.MOD_ID, "textures/gui/turtle_advanced.png");
|
private static final ResourceLocation BACKGROUND_ADVANCED = ResourceLocation.fromNamespaceAndPath(ComputerCraftAPI.MOD_ID, "textures/gui/turtle_advanced.png");
|
||||||
|
|
||||||
|
private static final ResourceLocation SELECTED_NORMAL = ResourceLocation.fromNamespaceAndPath(ComputerCraftAPI.MOD_ID, "turtle_normal_selected_slot");
|
||||||
|
private static final ResourceLocation SELECTED_ADVANCED = ResourceLocation.fromNamespaceAndPath(ComputerCraftAPI.MOD_ID, "turtle_advanced_selected_slot");
|
||||||
|
|
||||||
private static final int TEX_WIDTH = 278;
|
private static final int TEX_WIDTH = 278;
|
||||||
private static final int TEX_HEIGHT = 217;
|
private static final int TEX_HEIGHT = 217;
|
||||||
|
|
||||||
@ -54,9 +57,9 @@ public class TurtleScreen extends AbstractComputerScreen<TurtleMenu> {
|
|||||||
if (slot >= 0) {
|
if (slot >= 0) {
|
||||||
var slotX = slot % 4;
|
var slotX = slot % 4;
|
||||||
var slotY = slot / 4;
|
var slotY = slot / 4;
|
||||||
graphics.blit(texture,
|
graphics.blitSprite(
|
||||||
leftPos + TURTLE_START_X - 2 + slotX * 18, topPos + PLAYER_START_Y - 2 + slotY * 18, 0,
|
advanced ? SELECTED_ADVANCED : SELECTED_NORMAL,
|
||||||
0, 217, 24, 24, FULL_TEX_SIZE, FULL_TEX_SIZE
|
leftPos + TURTLE_START_X - 2 + slotX * 18, topPos + PLAYER_START_Y - 2 + slotY * 18, 0, 22, 22
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -55,7 +55,7 @@ public final class ComputerSidebar {
|
|||||||
add.accept(new DynamicImageButton(
|
add.accept(new DynamicImageButton(
|
||||||
x, y, ICON_WIDTH, ICON_HEIGHT,
|
x, y, ICON_WIDTH, ICON_HEIGHT,
|
||||||
GuiSprites.TERMINATE::get,
|
GuiSprites.TERMINATE::get,
|
||||||
b -> input.queueEvent("terminate"),
|
b -> input.terminate(),
|
||||||
new HintedMessage(
|
new HintedMessage(
|
||||||
Component.translatable("gui.computercraft.tooltip.terminate"),
|
Component.translatable("gui.computercraft.tooltip.terminate"),
|
||||||
Component.translatable("gui.computercraft.tooltip.terminate.key")
|
Component.translatable("gui.computercraft.tooltip.terminate.key")
|
||||||
|
@ -10,8 +10,8 @@ import net.minecraft.ChatFormatting;
|
|||||||
import net.minecraft.client.gui.GuiGraphics;
|
import net.minecraft.client.gui.GuiGraphics;
|
||||||
import net.minecraft.client.gui.components.Button;
|
import net.minecraft.client.gui.components.Button;
|
||||||
import net.minecraft.client.gui.components.Tooltip;
|
import net.minecraft.client.gui.components.Tooltip;
|
||||||
import net.minecraft.client.renderer.texture.TextureAtlasSprite;
|
|
||||||
import net.minecraft.network.chat.Component;
|
import net.minecraft.network.chat.Component;
|
||||||
|
import net.minecraft.resources.ResourceLocation;
|
||||||
|
|
||||||
import javax.annotation.Nullable;
|
import javax.annotation.Nullable;
|
||||||
import java.util.function.Supplier;
|
import java.util.function.Supplier;
|
||||||
@ -21,11 +21,11 @@ import java.util.function.Supplier;
|
|||||||
* dynamically.
|
* dynamically.
|
||||||
*/
|
*/
|
||||||
public class DynamicImageButton extends Button {
|
public class DynamicImageButton extends Button {
|
||||||
private final Boolean2ObjectFunction<TextureAtlasSprite> texture;
|
private final Boolean2ObjectFunction<ResourceLocation> texture;
|
||||||
private final Supplier<HintedMessage> message;
|
private final Supplier<HintedMessage> message;
|
||||||
|
|
||||||
public DynamicImageButton(
|
public DynamicImageButton(
|
||||||
int x, int y, int width, int height, Boolean2ObjectFunction<TextureAtlasSprite> texture, OnPress onPress,
|
int x, int y, int width, int height, Boolean2ObjectFunction<ResourceLocation> texture, OnPress onPress,
|
||||||
HintedMessage message
|
HintedMessage message
|
||||||
) {
|
) {
|
||||||
this(x, y, width, height, texture, onPress, () -> message);
|
this(x, y, width, height, texture, onPress, () -> message);
|
||||||
@ -33,7 +33,7 @@ public class DynamicImageButton extends Button {
|
|||||||
|
|
||||||
public DynamicImageButton(
|
public DynamicImageButton(
|
||||||
int x, int y, int width, int height,
|
int x, int y, int width, int height,
|
||||||
Boolean2ObjectFunction<TextureAtlasSprite> texture,
|
Boolean2ObjectFunction<ResourceLocation> texture,
|
||||||
OnPress onPress, Supplier<HintedMessage> message
|
OnPress onPress, Supplier<HintedMessage> message
|
||||||
) {
|
) {
|
||||||
super(x, y, width, height, Component.empty(), onPress, DEFAULT_NARRATION);
|
super(x, y, width, height, Component.empty(), onPress, DEFAULT_NARRATION);
|
||||||
@ -50,7 +50,7 @@ public class DynamicImageButton extends Button {
|
|||||||
var texture = this.texture.get(isHoveredOrFocused());
|
var texture = this.texture.get(isHoveredOrFocused());
|
||||||
|
|
||||||
RenderSystem.disableDepthTest();
|
RenderSystem.disableDepthTest();
|
||||||
graphics.blit(getX(), getY(), 0, width, height, texture);
|
graphics.blitSprite(texture, getX(), getY(), 0, width, height);
|
||||||
RenderSystem.enableDepthTest();
|
RenderSystem.enableDepthTest();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -69,11 +69,8 @@ public class TerminalWidget extends AbstractWidget {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean charTyped(char ch, int modifiers) {
|
public boolean charTyped(char ch, int modifiers) {
|
||||||
if (ch >= 32 && ch <= 126 || ch >= 160 && ch <= 255) {
|
var terminalChar = StringUtil.unicodeToTerminal(ch);
|
||||||
// Queue the char event for any printable chars in byte range
|
if (StringUtil.isTypableChar(terminalChar)) computer.charTyped(terminalChar);
|
||||||
computer.queueEvent("char", new Object[]{ Character.toString(ch) });
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -110,8 +107,8 @@ public class TerminalWidget extends AbstractWidget {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void paste() {
|
private void paste() {
|
||||||
var clipboard = StringUtil.normaliseClipboardString(Minecraft.getInstance().keyboardHandler.getClipboard());
|
var clipboard = StringUtil.getClipboardString(Minecraft.getInstance().keyboardHandler.getClipboard());
|
||||||
if (!clipboard.isEmpty()) computer.queueEvent("paste", new Object[]{ clipboard });
|
if (clipboard.remaining() > 0) computer.paste(clipboard);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -220,7 +217,7 @@ public class TerminalWidget extends AbstractWidget {
|
|||||||
|
|
||||||
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) {
|
||||||
computer.queueEvent("terminate");
|
computer.terminate();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (shutdownTimer >= 0 && shutdownTimer < TERMINATE_TIME && (shutdownTimer += 0.05f) > TERMINATE_TIME) {
|
if (shutdownTimer >= 0 && shutdownTimer < TERMINATE_TIME && (shutdownTimer += 0.05f) > TERMINATE_TIME) {
|
||||||
|
@ -39,8 +39,8 @@ public final class PocketItemRenderer extends ItemMapLikeRenderer {
|
|||||||
|
|
||||||
int termWidth, termHeight;
|
int termWidth, termHeight;
|
||||||
if (terminal == null) {
|
if (terminal == null) {
|
||||||
termWidth = Config.pocketTermWidth;
|
termWidth = Config.DEFAULT_POCKET_TERM_WIDTH;
|
||||||
termHeight = Config.pocketTermHeight;
|
termHeight = Config.DEFAULT_POCKET_TERM_HEIGHT;
|
||||||
} else {
|
} else {
|
||||||
termWidth = terminal.getWidth();
|
termWidth = terminal.getWidth();
|
||||||
termHeight = terminal.getHeight();
|
termHeight = terminal.getHeight();
|
||||||
|
@ -74,10 +74,6 @@ public final class DataProviders {
|
|||||||
LecternPrintoutModel.TEXTURE
|
LecternPrintoutModel.TEXTURE
|
||||||
)));
|
)));
|
||||||
out.accept(GuiSprites.SPRITE_SHEET, makeSprites(
|
out.accept(GuiSprites.SPRITE_SHEET, makeSprites(
|
||||||
// Buttons
|
|
||||||
GuiSprites.TURNED_OFF.textures(),
|
|
||||||
GuiSprites.TURNED_ON.textures(),
|
|
||||||
GuiSprites.TERMINATE.textures(),
|
|
||||||
// Computers
|
// Computers
|
||||||
GuiSprites.COMPUTER_NORMAL.textures(),
|
GuiSprites.COMPUTER_NORMAL.textures(),
|
||||||
GuiSprites.COMPUTER_ADVANCED.textures(),
|
GuiSprites.COMPUTER_ADVANCED.textures(),
|
||||||
|
@ -1,11 +1,5 @@
|
|||||||
{
|
{
|
||||||
"sources": [
|
"sources": [
|
||||||
{"type": "minecraft:single", "resource": "computercraft:gui/buttons/turned_off"},
|
|
||||||
{"type": "minecraft:single", "resource": "computercraft:gui/buttons/turned_off_hover"},
|
|
||||||
{"type": "minecraft:single", "resource": "computercraft:gui/buttons/turned_on"},
|
|
||||||
{"type": "minecraft:single", "resource": "computercraft:gui/buttons/turned_on_hover"},
|
|
||||||
{"type": "minecraft:single", "resource": "computercraft:gui/buttons/terminate"},
|
|
||||||
{"type": "minecraft:single", "resource": "computercraft:gui/buttons/terminate_hover"},
|
|
||||||
{"type": "minecraft:single", "resource": "computercraft:gui/border_normal"},
|
{"type": "minecraft:single", "resource": "computercraft:gui/border_normal"},
|
||||||
{"type": "minecraft:single", "resource": "computercraft:gui/pocket_bottom_normal"},
|
{"type": "minecraft:single", "resource": "computercraft:gui/pocket_bottom_normal"},
|
||||||
{"type": "minecraft:single", "resource": "computercraft:gui/sidebar_normal"},
|
{"type": "minecraft:single", "resource": "computercraft:gui/sidebar_normal"},
|
||||||
|
@ -11,8 +11,7 @@ import dan200.computercraft.shared.computer.core.ComputerFamily;
|
|||||||
import dan200.computercraft.shared.computer.core.ComputerState;
|
import dan200.computercraft.shared.computer.core.ComputerState;
|
||||||
import dan200.computercraft.shared.computer.core.ServerComputer;
|
import dan200.computercraft.shared.computer.core.ServerComputer;
|
||||||
import dan200.computercraft.shared.computer.inventory.ComputerMenuWithoutInventory;
|
import dan200.computercraft.shared.computer.inventory.ComputerMenuWithoutInventory;
|
||||||
import dan200.computercraft.shared.config.Config;
|
import dan200.computercraft.shared.config.ConfigSpec;
|
||||||
import dan200.computercraft.shared.util.ComponentMap;
|
|
||||||
import net.minecraft.core.BlockPos;
|
import net.minecraft.core.BlockPos;
|
||||||
import net.minecraft.core.Direction;
|
import net.minecraft.core.Direction;
|
||||||
import net.minecraft.server.level.ServerLevel;
|
import net.minecraft.server.level.ServerLevel;
|
||||||
@ -33,10 +32,9 @@ public class ComputerBlockEntity extends AbstractComputerBlockEntity {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected ServerComputer createComputer(int id) {
|
protected ServerComputer createComputer(int id) {
|
||||||
return new ServerComputer(
|
return new ServerComputer((ServerLevel) getLevel(), getBlockPos(), ServerComputer.properties(id, getFamily())
|
||||||
(ServerLevel) getLevel(), getBlockPos(), id, label,
|
.label(getLabel())
|
||||||
getFamily(), Config.computerTermWidth, Config.computerTermHeight,
|
.terminalSize(ConfigSpec.computerTermWidth.get(), ConfigSpec.computerTermHeight.get())
|
||||||
ComponentMap.empty()
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -6,44 +6,33 @@ package dan200.computercraft.shared.computer.core;
|
|||||||
|
|
||||||
import dan200.computercraft.shared.computer.menu.ServerInputHandler;
|
import dan200.computercraft.shared.computer.menu.ServerInputHandler;
|
||||||
|
|
||||||
import javax.annotation.Nullable;
|
import java.nio.ByteBuffer;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Handles user-provided input, forwarding it to a computer. This is used
|
* Handles user-provided input, forwarding it to a computer. This describes the "shape" of both the client-and
|
||||||
|
* server-side input handlers.
|
||||||
*
|
*
|
||||||
* @see ServerInputHandler
|
* @see ServerInputHandler
|
||||||
* @see ServerComputer
|
* @see ServerComputer
|
||||||
*/
|
*/
|
||||||
public interface InputHandler {
|
public interface InputHandler {
|
||||||
void queueEvent(String event, @Nullable Object[] arguments);
|
void keyDown(int key, boolean repeat);
|
||||||
|
|
||||||
default void queueEvent(String event) {
|
void keyUp(int key);
|
||||||
queueEvent(event, null);
|
|
||||||
}
|
|
||||||
|
|
||||||
default void keyDown(int key, boolean repeat) {
|
void charTyped(byte chr);
|
||||||
queueEvent("key", new Object[]{ key, repeat });
|
|
||||||
}
|
|
||||||
|
|
||||||
default void keyUp(int key) {
|
void paste(ByteBuffer contents);
|
||||||
queueEvent("key_up", new Object[]{ key });
|
|
||||||
}
|
|
||||||
|
|
||||||
default void mouseClick(int button, int x, int y) {
|
void mouseClick(int button, int x, int y);
|
||||||
queueEvent("mouse_click", new Object[]{ button, x, y });
|
|
||||||
}
|
|
||||||
|
|
||||||
default void mouseUp(int button, int x, int y) {
|
void mouseUp(int button, int x, int y);
|
||||||
queueEvent("mouse_up", new Object[]{ button, x, y });
|
|
||||||
}
|
|
||||||
|
|
||||||
default void mouseDrag(int button, int x, int y) {
|
void mouseDrag(int button, int x, int y);
|
||||||
queueEvent("mouse_drag", new Object[]{ button, x, y });
|
|
||||||
}
|
|
||||||
|
|
||||||
default void mouseScroll(int direction, int x, int y) {
|
void mouseScroll(int direction, int x, int y);
|
||||||
queueEvent("mouse_scroll", new Object[]{ direction, x, y });
|
|
||||||
}
|
void terminate();
|
||||||
|
|
||||||
void shutdown();
|
void shutdown();
|
||||||
|
|
||||||
|
@ -6,12 +6,14 @@ package dan200.computercraft.shared.computer.core;
|
|||||||
|
|
||||||
import dan200.computercraft.api.ComputerCraftAPI;
|
import dan200.computercraft.api.ComputerCraftAPI;
|
||||||
import dan200.computercraft.api.component.AdminComputer;
|
import dan200.computercraft.api.component.AdminComputer;
|
||||||
|
import dan200.computercraft.api.component.ComputerComponent;
|
||||||
import dan200.computercraft.api.component.ComputerComponents;
|
import dan200.computercraft.api.component.ComputerComponents;
|
||||||
import dan200.computercraft.api.filesystem.WritableMount;
|
import dan200.computercraft.api.filesystem.WritableMount;
|
||||||
import dan200.computercraft.api.peripheral.IPeripheral;
|
import dan200.computercraft.api.peripheral.IPeripheral;
|
||||||
import dan200.computercraft.api.peripheral.WorkMonitor;
|
import dan200.computercraft.api.peripheral.WorkMonitor;
|
||||||
import dan200.computercraft.core.computer.Computer;
|
import dan200.computercraft.core.computer.Computer;
|
||||||
import dan200.computercraft.core.computer.ComputerEnvironment;
|
import dan200.computercraft.core.computer.ComputerEnvironment;
|
||||||
|
import dan200.computercraft.core.computer.ComputerEvents;
|
||||||
import dan200.computercraft.core.computer.ComputerSide;
|
import dan200.computercraft.core.computer.ComputerSide;
|
||||||
import dan200.computercraft.core.metrics.MetricsObserver;
|
import dan200.computercraft.core.metrics.MetricsObserver;
|
||||||
import dan200.computercraft.impl.ApiFactories;
|
import dan200.computercraft.impl.ApiFactories;
|
||||||
@ -34,7 +36,7 @@ import java.util.UUID;
|
|||||||
import java.util.concurrent.atomic.AtomicBoolean;
|
import java.util.concurrent.atomic.AtomicBoolean;
|
||||||
import java.util.function.Function;
|
import java.util.function.Function;
|
||||||
|
|
||||||
public class ServerComputer implements InputHandler, ComputerEnvironment {
|
public class ServerComputer implements ComputerEnvironment, ComputerEvents.Receiver {
|
||||||
private final UUID instanceUUID = UUID.randomUUID();
|
private final UUID instanceUUID = UUID.randomUUID();
|
||||||
|
|
||||||
private ServerLevel level;
|
private ServerLevel level;
|
||||||
@ -49,16 +51,21 @@ public class ServerComputer implements InputHandler, ComputerEnvironment {
|
|||||||
|
|
||||||
private int ticksSincePing;
|
private int ticksSincePing;
|
||||||
|
|
||||||
|
@Deprecated
|
||||||
public ServerComputer(
|
public ServerComputer(
|
||||||
ServerLevel level, BlockPos position, int computerID, @Nullable String label, ComputerFamily family, int terminalWidth, int terminalHeight,
|
ServerLevel level, BlockPos position, int computerID, @Nullable String label, ComputerFamily family, int terminalWidth, int terminalHeight,
|
||||||
ComponentMap baseComponents
|
ComponentMap baseComponents
|
||||||
) {
|
) {
|
||||||
|
this(level, position, properties(computerID, family).label(label).terminalSize(terminalWidth, terminalHeight).addComponents(baseComponents));
|
||||||
|
}
|
||||||
|
|
||||||
|
public ServerComputer(ServerLevel level, BlockPos position, Properties properties) {
|
||||||
this.level = level;
|
this.level = level;
|
||||||
this.position = position;
|
this.position = position;
|
||||||
this.family = family;
|
this.family = properties.family;
|
||||||
|
|
||||||
var context = ServerContext.get(level.getServer());
|
var context = ServerContext.get(level.getServer());
|
||||||
terminal = new NetworkedTerminal(terminalWidth, terminalHeight, family != ComputerFamily.NORMAL, this::markTerminalChanged);
|
terminal = new NetworkedTerminal(properties.terminalWidth, properties.terminalHeight, family != ComputerFamily.NORMAL, this::markTerminalChanged);
|
||||||
metrics = context.metrics().createMetricObserver(this);
|
metrics = context.metrics().createMetricObserver(this);
|
||||||
|
|
||||||
var componentBuilder = ComponentMap.builder();
|
var componentBuilder = ComponentMap.builder();
|
||||||
@ -67,11 +74,11 @@ public class ServerComputer implements InputHandler, ComputerEnvironment {
|
|||||||
componentBuilder.add(ComputerComponents.ADMIN_COMPUTER, new AdminComputer() {
|
componentBuilder.add(ComputerComponents.ADMIN_COMPUTER, new AdminComputer() {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
componentBuilder.add(baseComponents);
|
componentBuilder.add(properties.components.build());
|
||||||
var components = componentBuilder.build();
|
var components = componentBuilder.build();
|
||||||
|
|
||||||
computer = new Computer(context.computerContext(), this, terminal, computerID);
|
computer = new Computer(context.computerContext(), this, terminal, properties.computerID);
|
||||||
computer.setLabel(label);
|
computer.setLabel(properties.label);
|
||||||
|
|
||||||
// Load in the externally registered APIs.
|
// Load in the externally registered APIs.
|
||||||
for (var factory : ApiFactories.getAll()) {
|
for (var factory : ApiFactories.getAll()) {
|
||||||
@ -84,24 +91,24 @@ public class ServerComputer implements InputHandler, ComputerEnvironment {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public ComputerFamily getFamily() {
|
public final ComputerFamily getFamily() {
|
||||||
return family;
|
return family;
|
||||||
}
|
}
|
||||||
|
|
||||||
public ServerLevel getLevel() {
|
public final ServerLevel getLevel() {
|
||||||
return level;
|
return level;
|
||||||
}
|
}
|
||||||
|
|
||||||
public BlockPos getPosition() {
|
public final BlockPos getPosition() {
|
||||||
return position;
|
return position;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setPosition(ServerLevel level, BlockPos pos) {
|
public final void setPosition(ServerLevel level, BlockPos pos) {
|
||||||
this.level = level;
|
this.level = level;
|
||||||
position = pos.immutable();
|
position = pos.immutable();
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void markTerminalChanged() {
|
protected final void markTerminalChanged() {
|
||||||
terminalChanged.set(true);
|
terminalChanged.set(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -115,11 +122,11 @@ public class ServerComputer implements InputHandler, ComputerEnvironment {
|
|||||||
sendToAllInteracting(c -> new ComputerTerminalClientMessage(c, getTerminalState()));
|
sendToAllInteracting(c -> new ComputerTerminalClientMessage(c, getTerminalState()));
|
||||||
}
|
}
|
||||||
|
|
||||||
public TerminalState getTerminalState() {
|
public final TerminalState getTerminalState() {
|
||||||
return TerminalState.create(terminal);
|
return TerminalState.create(terminal);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void keepAlive() {
|
public final void keepAlive() {
|
||||||
ticksSincePing = 0;
|
ticksSincePing = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -132,7 +139,7 @@ public class ServerComputer implements InputHandler, ComputerEnvironment {
|
|||||||
*
|
*
|
||||||
* @return What sides on the computer have changed.
|
* @return What sides on the computer have changed.
|
||||||
*/
|
*/
|
||||||
public int pollRedstoneChanges() {
|
public final int pollRedstoneChanges() {
|
||||||
return computer.pollRedstoneChanges();
|
return computer.pollRedstoneChanges();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -145,7 +152,7 @@ public class ServerComputer implements InputHandler, ComputerEnvironment {
|
|||||||
computer.unload();
|
computer.unload();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void close() {
|
public final void close() {
|
||||||
unload();
|
unload();
|
||||||
ServerContext.get(level.getServer()).registry().remove(this);
|
ServerContext.get(level.getServer()).registry().remove(this);
|
||||||
}
|
}
|
||||||
@ -165,7 +172,7 @@ public class ServerComputer implements InputHandler, ComputerEnvironment {
|
|||||||
var server = level.getServer();
|
var server = level.getServer();
|
||||||
|
|
||||||
for (var player : server.getPlayerList().getPlayers()) {
|
for (var player : server.getPlayerList().getPlayers()) {
|
||||||
if (player.containerMenu instanceof ComputerMenu && ((ComputerMenu) player.containerMenu).getComputer() == this) {
|
if (player.containerMenu instanceof ComputerMenu menu && menu.getComputer() == this) {
|
||||||
ServerNetworking.sendToPlayer(createPacket.apply(player.containerMenu), player);
|
ServerNetworking.sendToPlayer(createPacket.apply(player.containerMenu), player);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -174,93 +181,136 @@ public class ServerComputer implements InputHandler, ComputerEnvironment {
|
|||||||
protected void onRemoved() {
|
protected void onRemoved() {
|
||||||
}
|
}
|
||||||
|
|
||||||
public UUID getInstanceUUID() {
|
public final UUID getInstanceUUID() {
|
||||||
return instanceUUID;
|
return instanceUUID;
|
||||||
}
|
}
|
||||||
|
|
||||||
public int getID() {
|
public final int getID() {
|
||||||
return computer.getID();
|
return computer.getID();
|
||||||
}
|
}
|
||||||
|
|
||||||
public @Nullable String getLabel() {
|
public final @Nullable String getLabel() {
|
||||||
return computer.getLabel();
|
return computer.getLabel();
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean isOn() {
|
public final boolean isOn() {
|
||||||
return computer.isOn();
|
return computer.isOn();
|
||||||
}
|
}
|
||||||
|
|
||||||
public ComputerState getState() {
|
public final ComputerState getState() {
|
||||||
if (!computer.isOn()) return ComputerState.OFF;
|
if (!computer.isOn()) return ComputerState.OFF;
|
||||||
return computer.isBlinking() ? ComputerState.BLINKING : ComputerState.ON;
|
return computer.isBlinking() ? ComputerState.BLINKING : ComputerState.ON;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
public final void turnOn() {
|
||||||
public void turnOn() {
|
|
||||||
computer.turnOn();
|
computer.turnOn();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
public final void shutdown() {
|
||||||
public void shutdown() {
|
|
||||||
computer.shutdown();
|
computer.shutdown();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
public final void reboot() {
|
||||||
public void reboot() {
|
|
||||||
computer.reboot();
|
computer.reboot();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void queueEvent(String event, @Nullable Object[] arguments) {
|
public final void queueEvent(String event, @Nullable Object[] arguments) {
|
||||||
computer.queueEvent(event, arguments);
|
computer.queueEvent(event, arguments);
|
||||||
}
|
}
|
||||||
|
|
||||||
public int getRedstoneOutput(ComputerSide side) {
|
public final void queueEvent(String event) {
|
||||||
|
queueEvent(event, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public final int getRedstoneOutput(ComputerSide side) {
|
||||||
return computer.isOn() ? computer.getRedstone().getExternalOutput(side) : 0;
|
return computer.isOn() ? computer.getRedstone().getExternalOutput(side) : 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setRedstoneInput(ComputerSide side, int level, int bundledState) {
|
public final void setRedstoneInput(ComputerSide side, int level, int bundledState) {
|
||||||
computer.getRedstone().setInput(side, level, bundledState);
|
computer.getRedstone().setInput(side, level, bundledState);
|
||||||
}
|
}
|
||||||
|
|
||||||
public int getBundledRedstoneOutput(ComputerSide side) {
|
public final int getBundledRedstoneOutput(ComputerSide side) {
|
||||||
return computer.isOn() ? computer.getRedstone().getExternalBundledOutput(side) : 0;
|
return computer.isOn() ? computer.getRedstone().getExternalBundledOutput(side) : 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setPeripheral(ComputerSide side, @Nullable IPeripheral peripheral) {
|
public final void setPeripheral(ComputerSide side, @Nullable IPeripheral peripheral) {
|
||||||
computer.getEnvironment().setPeripheral(side, peripheral);
|
computer.getEnvironment().setPeripheral(side, peripheral);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Nullable
|
@Nullable
|
||||||
public IPeripheral getPeripheral(ComputerSide side) {
|
public final IPeripheral getPeripheral(ComputerSide side) {
|
||||||
return computer.getEnvironment().getPeripheral(side);
|
return computer.getEnvironment().getPeripheral(side);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setLabel(@Nullable String label) {
|
public final void setLabel(@Nullable String label) {
|
||||||
computer.setLabel(label);
|
computer.setLabel(label);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public double getTimeOfDay() {
|
public final double getTimeOfDay() {
|
||||||
return (level.getDayTime() + 6000) % 24000 / 1000.0;
|
return (level.getDayTime() + 6000) % 24000 / 1000.0;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int getDay() {
|
public final int getDay() {
|
||||||
return (int) ((level.getDayTime() + 6000) / 24000) + 1;
|
return (int) ((level.getDayTime() + 6000) / 24000) + 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public MetricsObserver getMetrics() {
|
public final MetricsObserver getMetrics() {
|
||||||
return metrics;
|
return metrics;
|
||||||
}
|
}
|
||||||
|
|
||||||
public WorkMonitor getMainThreadMonitor() {
|
public final WorkMonitor getMainThreadMonitor() {
|
||||||
return computer.getMainThreadMonitor();
|
return computer.getMainThreadMonitor();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public @Nullable WritableMount createRootMount() {
|
public final WritableMount createRootMount() {
|
||||||
return ComputerCraftAPI.createSaveDirMount(level.getServer(), "computer/" + computer.getID(), Config.computerSpaceLimit);
|
return ComputerCraftAPI.createSaveDirMount(level.getServer(), "computer/" + computer.getID(), Config.computerSpaceLimit);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static Properties properties(int computerID, ComputerFamily family) {
|
||||||
|
return new Properties(computerID, family);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static final class Properties {
|
||||||
|
|
||||||
|
private final int computerID;
|
||||||
|
private @Nullable String label;
|
||||||
|
private final ComputerFamily family;
|
||||||
|
|
||||||
|
private int terminalWidth = Config.DEFAULT_COMPUTER_TERM_WIDTH;
|
||||||
|
private int terminalHeight = Config.DEFAULT_COMPUTER_TERM_HEIGHT;
|
||||||
|
private final ComponentMap.Builder components = ComponentMap.builder();
|
||||||
|
|
||||||
|
private Properties(int computerID, ComputerFamily family) {
|
||||||
|
this.computerID = computerID;
|
||||||
|
this.family = family;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Properties label(@Nullable String label) {
|
||||||
|
this.label = label;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Properties terminalSize(int width, int height) {
|
||||||
|
if (width <= 0 || height <= 0) throw new IllegalArgumentException("Terminal size must be positive");
|
||||||
|
this.terminalWidth = width;
|
||||||
|
this.terminalHeight = height;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public <T> Properties addComponent(ComputerComponent<T> component, T value) {
|
||||||
|
components.add(component, value);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
private Properties addComponents(ComponentMap components) {
|
||||||
|
this.components.add(components);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -7,6 +7,8 @@ package dan200.computercraft.shared.computer.menu;
|
|||||||
import dan200.computercraft.core.apis.handles.ByteBufferChannel;
|
import dan200.computercraft.core.apis.handles.ByteBufferChannel;
|
||||||
import dan200.computercraft.core.apis.transfer.TransferredFile;
|
import dan200.computercraft.core.apis.transfer.TransferredFile;
|
||||||
import dan200.computercraft.core.apis.transfer.TransferredFiles;
|
import dan200.computercraft.core.apis.transfer.TransferredFiles;
|
||||||
|
import dan200.computercraft.core.computer.ComputerEvents;
|
||||||
|
import dan200.computercraft.core.util.StringUtil;
|
||||||
import dan200.computercraft.shared.computer.upload.FileSlice;
|
import dan200.computercraft.shared.computer.upload.FileSlice;
|
||||||
import dan200.computercraft.shared.computer.upload.FileUpload;
|
import dan200.computercraft.shared.computer.upload.FileUpload;
|
||||||
import dan200.computercraft.shared.computer.upload.UploadResult;
|
import dan200.computercraft.shared.computer.upload.UploadResult;
|
||||||
@ -21,6 +23,7 @@ import org.slf4j.Logger;
|
|||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
import javax.annotation.Nullable;
|
import javax.annotation.Nullable;
|
||||||
|
import java.nio.ByteBuffer;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
|
|
||||||
@ -48,21 +51,33 @@ public class ServerInputState<T extends AbstractContainerMenu & ComputerMenu> im
|
|||||||
this.owner = owner;
|
this.owner = owner;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public void queueEvent(String event, @Nullable Object[] arguments) {
|
|
||||||
owner.getComputer().queueEvent(event, arguments);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void keyDown(int key, boolean repeat) {
|
public void keyDown(int key, boolean repeat) {
|
||||||
keysDown.add(key);
|
keysDown.add(key);
|
||||||
owner.getComputer().keyDown(key, repeat);
|
ComputerEvents.keyDown(owner.getComputer(), key, repeat);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void keyUp(int key) {
|
public void keyUp(int key) {
|
||||||
keysDown.remove(key);
|
keysDown.remove(key);
|
||||||
owner.getComputer().keyUp(key);
|
ComputerEvents.keyUp(owner.getComputer(), key);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void charTyped(byte chr) {
|
||||||
|
if (StringUtil.isTypableChar(chr)) ComputerEvents.charTyped(owner.getComputer(), chr);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void paste(ByteBuffer contents) {
|
||||||
|
if (contents.remaining() > 0 && isValidClipboard(contents)) ComputerEvents.paste(owner.getComputer(), contents);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static boolean isValidClipboard(ByteBuffer buffer) {
|
||||||
|
for (int i = buffer.remaining(), max = buffer.limit(); i < max; i++) {
|
||||||
|
if (!StringUtil.isTypableChar(buffer.get(i))) return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -71,7 +86,7 @@ public class ServerInputState<T extends AbstractContainerMenu & ComputerMenu> im
|
|||||||
lastMouseY = y;
|
lastMouseY = y;
|
||||||
lastMouseDown = button;
|
lastMouseDown = button;
|
||||||
|
|
||||||
owner.getComputer().mouseClick(button, x, y);
|
ComputerEvents.mouseClick(owner.getComputer(), button, x, y);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -80,7 +95,7 @@ public class ServerInputState<T extends AbstractContainerMenu & ComputerMenu> im
|
|||||||
lastMouseY = y;
|
lastMouseY = y;
|
||||||
lastMouseDown = -1;
|
lastMouseDown = -1;
|
||||||
|
|
||||||
owner.getComputer().mouseUp(button, x, y);
|
ComputerEvents.mouseUp(owner.getComputer(), button, x, y);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -89,7 +104,7 @@ public class ServerInputState<T extends AbstractContainerMenu & ComputerMenu> im
|
|||||||
lastMouseY = y;
|
lastMouseY = y;
|
||||||
lastMouseDown = button;
|
lastMouseDown = button;
|
||||||
|
|
||||||
owner.getComputer().mouseDrag(button, x, y);
|
ComputerEvents.mouseDrag(owner.getComputer(), button, x, y);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -97,7 +112,12 @@ public class ServerInputState<T extends AbstractContainerMenu & ComputerMenu> im
|
|||||||
lastMouseX = x;
|
lastMouseX = x;
|
||||||
lastMouseY = y;
|
lastMouseY = y;
|
||||||
|
|
||||||
owner.getComputer().mouseScroll(direction, x, y);
|
ComputerEvents.mouseScroll(owner.getComputer(), direction, x, y);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void terminate() {
|
||||||
|
owner.getComputer().queueEvent("terminate");
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -169,9 +189,9 @@ public class ServerInputState<T extends AbstractContainerMenu & ComputerMenu> im
|
|||||||
public void close() {
|
public void close() {
|
||||||
var computer = owner.getComputer();
|
var computer = owner.getComputer();
|
||||||
var keys = keysDown.iterator();
|
var keys = keysDown.iterator();
|
||||||
while (keys.hasNext()) computer.keyUp(keys.nextInt());
|
while (keys.hasNext()) ComputerEvents.keyUp(computer, keys.nextInt());
|
||||||
|
|
||||||
if (lastMouseDown != -1) computer.mouseUp(lastMouseDown, lastMouseX, lastMouseY);
|
if (lastMouseDown != -1) ComputerEvents.mouseUp(computer, lastMouseDown, lastMouseX, lastMouseY);
|
||||||
|
|
||||||
keysDown.clear();
|
keysDown.clear();
|
||||||
lastMouseDown = -1;
|
lastMouseDown = -1;
|
||||||
|
@ -32,14 +32,14 @@ public final class Config {
|
|||||||
public static int advancedTurtleFuelLimit = 100000;
|
public static int advancedTurtleFuelLimit = 100000;
|
||||||
public static boolean turtlesCanPush = true;
|
public static boolean turtlesCanPush = true;
|
||||||
|
|
||||||
public static int computerTermWidth = 51;
|
public static final int DEFAULT_COMPUTER_TERM_WIDTH = 51;
|
||||||
public static int computerTermHeight = 19;
|
public static final int DEFAULT_COMPUTER_TERM_HEIGHT = 19;
|
||||||
|
|
||||||
public static final int turtleTermWidth = 39;
|
public static final int TURTLE_TERM_WIDTH = 39;
|
||||||
public static final int turtleTermHeight = 13;
|
public static final int TURTLE_TERM_HEIGHT = 13;
|
||||||
|
|
||||||
public static int pocketTermWidth = 26;
|
public static final int DEFAULT_POCKET_TERM_WIDTH = 26;
|
||||||
public static int pocketTermHeight = 20;
|
public static final int DEFAULT_POCKET_TERM_HEIGHT = 20;
|
||||||
|
|
||||||
public static int monitorWidth = 8;
|
public static int monitorWidth = 8;
|
||||||
public static int monitorHeight = 6;
|
public static int monitorHeight = 6;
|
||||||
|
@ -344,13 +344,13 @@ public final class ConfigSpec {
|
|||||||
.push("term_sizes");
|
.push("term_sizes");
|
||||||
|
|
||||||
builder.comment("Terminal size of computers.").push("computer");
|
builder.comment("Terminal size of computers.").push("computer");
|
||||||
computerTermWidth = builder.comment("Width of computer terminal").defineInRange("width", Config.computerTermWidth, 1, 255);
|
computerTermWidth = builder.comment("Width of computer terminal").defineInRange("width", Config.DEFAULT_COMPUTER_TERM_WIDTH, 1, 255);
|
||||||
computerTermHeight = builder.comment("Height of computer terminal").defineInRange("height", Config.computerTermHeight, 1, 255);
|
computerTermHeight = builder.comment("Height of computer terminal").defineInRange("height", Config.DEFAULT_COMPUTER_TERM_HEIGHT, 1, 255);
|
||||||
builder.pop();
|
builder.pop();
|
||||||
|
|
||||||
builder.comment("Terminal size of pocket computers.").push("pocket_computer");
|
builder.comment("Terminal size of pocket computers.").push("pocket_computer");
|
||||||
pocketTermWidth = builder.comment("Width of pocket computer terminal").defineInRange("width", Config.pocketTermWidth, 1, 255);
|
pocketTermWidth = builder.comment("Width of pocket computer terminal").defineInRange("width", Config.DEFAULT_POCKET_TERM_WIDTH, 1, 255);
|
||||||
pocketTermHeight = builder.comment("Height of pocket computer terminal").defineInRange("height", Config.pocketTermHeight, 1, 255);
|
pocketTermHeight = builder.comment("Height of pocket computer terminal").defineInRange("height", Config.DEFAULT_POCKET_TERM_HEIGHT, 1, 255);
|
||||||
builder.pop();
|
builder.pop();
|
||||||
|
|
||||||
builder.comment("Maximum size of monitors (in blocks).").push("monitor");
|
builder.comment("Maximum size of monitors (in blocks).").push("monitor");
|
||||||
@ -437,10 +437,6 @@ public final class ConfigSpec {
|
|||||||
Config.turtlesCanPush = turtlesCanPush.get();
|
Config.turtlesCanPush = turtlesCanPush.get();
|
||||||
|
|
||||||
// Terminal size
|
// Terminal size
|
||||||
Config.computerTermWidth = computerTermWidth.get();
|
|
||||||
Config.computerTermHeight = computerTermHeight.get();
|
|
||||||
Config.pocketTermWidth = pocketTermWidth.get();
|
|
||||||
Config.pocketTermHeight = pocketTermHeight.get();
|
|
||||||
Config.monitorWidth = monitorWidth.get();
|
Config.monitorWidth = monitorWidth.get();
|
||||||
Config.monitorHeight = monitorHeight.get();
|
Config.monitorHeight = monitorHeight.get();
|
||||||
}
|
}
|
||||||
|
@ -26,9 +26,9 @@ public final class NetworkMessages {
|
|||||||
private static final List<CustomPacketPayload.TypeAndCodec<RegistryFriendlyByteBuf, ? extends NetworkMessage<ClientNetworkContext>>> clientMessages = new ArrayList<>();
|
private static final List<CustomPacketPayload.TypeAndCodec<RegistryFriendlyByteBuf, ? extends NetworkMessage<ClientNetworkContext>>> clientMessages = new ArrayList<>();
|
||||||
|
|
||||||
public static final CustomPacketPayload.Type<ComputerActionServerMessage> COMPUTER_ACTION = registerServerbound("computer_action", ComputerActionServerMessage.STREAM_CODEC);
|
public static final CustomPacketPayload.Type<ComputerActionServerMessage> COMPUTER_ACTION = registerServerbound("computer_action", ComputerActionServerMessage.STREAM_CODEC);
|
||||||
public static final CustomPacketPayload.Type<QueueEventServerMessage> QUEUE_EVENT = register(serverMessages, "queue_event", QueueEventServerMessage.STREAM_CODEC);
|
|
||||||
public static final CustomPacketPayload.Type<KeyEventServerMessage> KEY_EVENT = registerServerbound("key_event", KeyEventServerMessage.STREAM_CODEC);
|
public static final CustomPacketPayload.Type<KeyEventServerMessage> KEY_EVENT = registerServerbound("key_event", KeyEventServerMessage.STREAM_CODEC);
|
||||||
public static final CustomPacketPayload.Type<MouseEventServerMessage> MOUSE_EVENT = registerServerbound("mouse_event", MouseEventServerMessage.STREAM_CODEC);
|
public static final CustomPacketPayload.Type<MouseEventServerMessage> MOUSE_EVENT = registerServerbound("mouse_event", MouseEventServerMessage.STREAM_CODEC);
|
||||||
|
public static final CustomPacketPayload.Type<PasteEventComputerMessage> PASTE_EVENT = registerServerbound("paste_event", PasteEventComputerMessage.STREAM_CODEC);
|
||||||
public static final CustomPacketPayload.Type<UploadFileMessage> UPLOAD_FILE = register(serverMessages, "upload_file", UploadFileMessage.STREAM_CODEC);
|
public static final CustomPacketPayload.Type<UploadFileMessage> UPLOAD_FILE = register(serverMessages, "upload_file", UploadFileMessage.STREAM_CODEC);
|
||||||
|
|
||||||
public static final CustomPacketPayload.Type<ChatTableClientMessage> CHAT_TABLE = registerClientbound("chat_table", ChatTableClientMessage.STREAM_CODEC);
|
public static final CustomPacketPayload.Type<ChatTableClientMessage> CHAT_TABLE = registerClientbound("chat_table", ChatTableClientMessage.STREAM_CODEC);
|
||||||
|
@ -83,25 +83,36 @@ public class MoreStreamCodecs {
|
|||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Equivalent to {@link ByteBufCodecs#BYTE_ARRAY}, but into an immutable {@link ByteBuffer}.
|
* Read a {@link ByteBuffer}, with a limit of the number of bytes to read.
|
||||||
|
*
|
||||||
|
* @param limit The maximum length of the received buffer.
|
||||||
|
* @return A stream codec that reads {@link ByteBuffer}s.
|
||||||
|
* @see #BYTE_BUFFER
|
||||||
*/
|
*/
|
||||||
public static final StreamCodec<ByteBuf, ByteBuffer> BYTE_BUFFER = new StreamCodec<>() {
|
public static StreamCodec<ByteBuf, ByteBuffer> byteBuffer(int limit) {
|
||||||
@Override
|
return new StreamCodec<>() {
|
||||||
public ByteBuffer decode(ByteBuf buf) {
|
@Override
|
||||||
var toRead = VarInt.read(buf);
|
public ByteBuffer decode(ByteBuf buf) {
|
||||||
if (toRead > buf.readableBytes()) {
|
var toRead = VarInt.read(buf);
|
||||||
throw new DecoderException("ByteArray with size " + toRead + " is bigger than allowed");
|
if (toRead > buf.readableBytes() || toRead >= limit) {
|
||||||
|
throw new DecoderException("ByteArray with size " + toRead + " is bigger than allowed");
|
||||||
|
}
|
||||||
|
|
||||||
|
var bytes = new byte[toRead];
|
||||||
|
buf.readBytes(bytes);
|
||||||
|
return ByteBuffer.wrap(bytes).asReadOnlyBuffer();
|
||||||
}
|
}
|
||||||
|
|
||||||
var bytes = new byte[toRead];
|
@Override
|
||||||
buf.readBytes(bytes);
|
public void encode(ByteBuf buf, ByteBuffer buffer) {
|
||||||
return ByteBuffer.wrap(bytes).asReadOnlyBuffer();
|
VarInt.write(buf, buffer.remaining());
|
||||||
}
|
buf.writeBytes(buffer.duplicate());
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
/**
|
||||||
public void encode(ByteBuf buf, ByteBuffer buffer) {
|
* Equivalent to {@link ByteBufCodecs#BYTE_ARRAY}, but into an immutable {@link ByteBuffer}.
|
||||||
VarInt.write(buf, buffer.remaining());
|
*/
|
||||||
buf.writeBytes(buffer.duplicate());
|
public static final StreamCodec<ByteBuf, ByteBuffer> BYTE_BUFFER = byteBuffer(Integer.MAX_VALUE);
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
@ -37,6 +37,7 @@ public final class ComputerActionServerMessage extends ComputerServerMessage {
|
|||||||
@Override
|
@Override
|
||||||
protected void handle(ServerNetworkContext context, ComputerMenu container) {
|
protected void handle(ServerNetworkContext context, ComputerMenu container) {
|
||||||
switch (action) {
|
switch (action) {
|
||||||
|
case TERMINATE -> container.getInput().terminate();
|
||||||
case TURN_ON -> container.getInput().turnOn();
|
case TURN_ON -> container.getInput().turnOn();
|
||||||
case REBOOT -> container.getInput().reboot();
|
case REBOOT -> container.getInput().reboot();
|
||||||
case SHUTDOWN -> container.getInput().shutdown();
|
case SHUTDOWN -> container.getInput().shutdown();
|
||||||
@ -49,6 +50,7 @@ public final class ComputerActionServerMessage extends ComputerServerMessage {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public enum Action {
|
public enum Action {
|
||||||
|
TERMINATE,
|
||||||
TURN_ON,
|
TURN_ON,
|
||||||
SHUTDOWN,
|
SHUTDOWN,
|
||||||
REBOOT
|
REBOOT
|
||||||
|
@ -25,8 +25,8 @@ public abstract class ComputerServerMessage implements NetworkMessage<ServerNetw
|
|||||||
@Override
|
@Override
|
||||||
public void handle(ServerNetworkContext context) {
|
public void handle(ServerNetworkContext context) {
|
||||||
Player player = context.getSender();
|
Player player = context.getSender();
|
||||||
if (player.containerMenu.containerId == containerId && player.containerMenu instanceof ComputerMenu) {
|
if (player.containerMenu.containerId == containerId && player.containerMenu instanceof ComputerMenu menu) {
|
||||||
handle(context, (ComputerMenu) player.containerMenu);
|
handle(context, menu);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -44,6 +44,7 @@ public final class KeyEventServerMessage extends ComputerServerMessage {
|
|||||||
case UP -> input.keyUp(key);
|
case UP -> input.keyUp(key);
|
||||||
case DOWN -> input.keyDown(key, false);
|
case DOWN -> input.keyDown(key, false);
|
||||||
case REPEAT -> input.keyDown(key, true);
|
case REPEAT -> input.keyDown(key, true);
|
||||||
|
case CHAR -> input.charTyped((byte) key);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -53,6 +54,6 @@ public final class KeyEventServerMessage extends ComputerServerMessage {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public enum Action {
|
public enum Action {
|
||||||
DOWN, REPEAT, UP
|
DOWN, REPEAT, UP, CHAR
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,53 @@
|
|||||||
|
// SPDX-FileCopyrightText: 2025 The CC: Tweaked Developers
|
||||||
|
//
|
||||||
|
// SPDX-License-Identifier: MPL-2.0
|
||||||
|
|
||||||
|
package dan200.computercraft.shared.network.server;
|
||||||
|
|
||||||
|
import dan200.computercraft.core.util.StringUtil;
|
||||||
|
import dan200.computercraft.shared.computer.core.ServerComputer;
|
||||||
|
import dan200.computercraft.shared.computer.menu.ComputerMenu;
|
||||||
|
import dan200.computercraft.shared.computer.menu.ServerInputHandler;
|
||||||
|
import dan200.computercraft.shared.network.NetworkMessages;
|
||||||
|
import dan200.computercraft.shared.network.codec.MoreStreamCodecs;
|
||||||
|
import net.minecraft.network.RegistryFriendlyByteBuf;
|
||||||
|
import net.minecraft.network.codec.ByteBufCodecs;
|
||||||
|
import net.minecraft.network.codec.StreamCodec;
|
||||||
|
import net.minecraft.network.protocol.common.custom.CustomPacketPayload;
|
||||||
|
import net.minecraft.world.inventory.AbstractContainerMenu;
|
||||||
|
|
||||||
|
import java.nio.ByteBuffer;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Paste a string on a {@link ServerComputer}.
|
||||||
|
*
|
||||||
|
* @see ServerInputHandler#paste(ByteBuffer)
|
||||||
|
*/
|
||||||
|
public class PasteEventComputerMessage extends ComputerServerMessage {
|
||||||
|
public static final StreamCodec<RegistryFriendlyByteBuf, PasteEventComputerMessage> STREAM_CODEC = StreamCodec.composite(
|
||||||
|
ByteBufCodecs.VAR_INT, PasteEventComputerMessage::containerId,
|
||||||
|
MoreStreamCodecs.byteBuffer(StringUtil.MAX_PASTE_LENGTH), c -> c.text,
|
||||||
|
PasteEventComputerMessage::new
|
||||||
|
);
|
||||||
|
|
||||||
|
private final ByteBuffer text;
|
||||||
|
|
||||||
|
public PasteEventComputerMessage(AbstractContainerMenu menu, ByteBuffer text) {
|
||||||
|
this(menu.containerId, text);
|
||||||
|
}
|
||||||
|
|
||||||
|
private PasteEventComputerMessage(int id, ByteBuffer text) {
|
||||||
|
super(id);
|
||||||
|
this.text = text;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void handle(ServerNetworkContext context, ComputerMenu container) {
|
||||||
|
container.getInput().paste(text);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public CustomPacketPayload.Type<PasteEventComputerMessage> type() {
|
||||||
|
return NetworkMessages.PASTE_EVENT;
|
||||||
|
}
|
||||||
|
}
|
@ -1,60 +0,0 @@
|
|||||||
// SPDX-FileCopyrightText: 2018 The CC: Tweaked Developers
|
|
||||||
//
|
|
||||||
// SPDX-License-Identifier: MPL-2.0
|
|
||||||
|
|
||||||
package dan200.computercraft.shared.network.server;
|
|
||||||
|
|
||||||
import dan200.computercraft.shared.computer.core.ServerComputer;
|
|
||||||
import dan200.computercraft.shared.computer.menu.ComputerMenu;
|
|
||||||
import dan200.computercraft.shared.computer.menu.ServerInputHandler;
|
|
||||||
import dan200.computercraft.shared.network.NetworkMessages;
|
|
||||||
import dan200.computercraft.shared.util.NBTUtil;
|
|
||||||
import net.minecraft.network.FriendlyByteBuf;
|
|
||||||
import net.minecraft.network.RegistryFriendlyByteBuf;
|
|
||||||
import net.minecraft.network.codec.StreamCodec;
|
|
||||||
import net.minecraft.network.protocol.common.custom.CustomPacketPayload;
|
|
||||||
import net.minecraft.world.inventory.AbstractContainerMenu;
|
|
||||||
|
|
||||||
import javax.annotation.Nullable;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Queue an event on a {@link ServerComputer}.
|
|
||||||
*
|
|
||||||
* @see ServerInputHandler#queueEvent(String)
|
|
||||||
*/
|
|
||||||
public final class QueueEventServerMessage extends ComputerServerMessage {
|
|
||||||
public static final StreamCodec<RegistryFriendlyByteBuf, QueueEventServerMessage> STREAM_CODEC = StreamCodec.ofMember(QueueEventServerMessage::write, QueueEventServerMessage::new);
|
|
||||||
|
|
||||||
private final String event;
|
|
||||||
private final @Nullable Object[] args;
|
|
||||||
|
|
||||||
public QueueEventServerMessage(AbstractContainerMenu menu, String event, @Nullable Object[] args) {
|
|
||||||
super(menu.containerId);
|
|
||||||
this.event = event;
|
|
||||||
this.args = args;
|
|
||||||
}
|
|
||||||
|
|
||||||
private QueueEventServerMessage(FriendlyByteBuf buf) {
|
|
||||||
super(buf.readVarInt());
|
|
||||||
event = buf.readUtf(Short.MAX_VALUE);
|
|
||||||
|
|
||||||
var args = buf.readNbt();
|
|
||||||
this.args = args == null ? null : NBTUtil.decodeObjects(args);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void write(RegistryFriendlyByteBuf buf) {
|
|
||||||
buf.writeVarInt(containerId());
|
|
||||||
buf.writeUtf(event);
|
|
||||||
buf.writeNbt(args == null ? null : NBTUtil.encodeObjects(args));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void handle(ServerNetworkContext context, ComputerMenu container) {
|
|
||||||
container.getInput().queueEvent(event, args);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public CustomPacketPayload.Type<QueueEventServerMessage> type() {
|
|
||||||
return NetworkMessages.QUEUE_EVENT;
|
|
||||||
}
|
|
||||||
}
|
|
@ -8,7 +8,7 @@ import dan200.computercraft.api.pocket.IPocketAccess;
|
|||||||
import dan200.computercraft.api.pocket.IPocketUpgrade;
|
import dan200.computercraft.api.pocket.IPocketUpgrade;
|
||||||
import dan200.computercraft.api.upgrades.UpgradeData;
|
import dan200.computercraft.api.upgrades.UpgradeData;
|
||||||
import dan200.computercraft.core.computer.ComputerSide;
|
import dan200.computercraft.core.computer.ComputerSide;
|
||||||
import dan200.computercraft.shared.computer.core.ComputerFamily;
|
import dan200.computercraft.shared.computer.core.ServerComputer;
|
||||||
import dan200.computercraft.shared.network.client.PocketComputerDataMessage;
|
import dan200.computercraft.shared.network.client.PocketComputerDataMessage;
|
||||||
import dan200.computercraft.shared.network.server.ServerNetworking;
|
import dan200.computercraft.shared.network.server.ServerNetworking;
|
||||||
import dan200.computercraft.shared.pocket.items.PocketComputerItem;
|
import dan200.computercraft.shared.pocket.items.PocketComputerItem;
|
||||||
@ -41,8 +41,8 @@ public final class PocketBrain implements IPocketAccess {
|
|||||||
private int colour = -1;
|
private int colour = -1;
|
||||||
private int lightColour = -1;
|
private int lightColour = -1;
|
||||||
|
|
||||||
public PocketBrain(PocketHolder holder, int computerID, @Nullable String label, ComputerFamily family, @Nullable UpgradeData<IPocketUpgrade> upgrade) {
|
public PocketBrain(PocketHolder holder, @Nullable UpgradeData<IPocketUpgrade> upgrade, ServerComputer.Properties properties) {
|
||||||
this.computer = new PocketServerComputer(this, holder, computerID, label, family);
|
this.computer = new PocketServerComputer(this, holder, properties);
|
||||||
this.holder = holder;
|
this.holder = holder;
|
||||||
this.position = holder.pos();
|
this.position = holder.pos();
|
||||||
this.upgrade = upgrade;
|
this.upgrade = upgrade;
|
||||||
|
@ -5,15 +5,13 @@
|
|||||||
package dan200.computercraft.shared.pocket.core;
|
package dan200.computercraft.shared.pocket.core;
|
||||||
|
|
||||||
import dan200.computercraft.api.component.ComputerComponents;
|
import dan200.computercraft.api.component.ComputerComponents;
|
||||||
import dan200.computercraft.shared.computer.core.ComputerFamily;
|
|
||||||
import dan200.computercraft.shared.computer.core.ComputerState;
|
import dan200.computercraft.shared.computer.core.ComputerState;
|
||||||
import dan200.computercraft.shared.computer.core.ServerComputer;
|
import dan200.computercraft.shared.computer.core.ServerComputer;
|
||||||
import dan200.computercraft.shared.config.Config;
|
import dan200.computercraft.shared.config.ConfigSpec;
|
||||||
import dan200.computercraft.shared.network.client.PocketComputerDataMessage;
|
import dan200.computercraft.shared.network.client.PocketComputerDataMessage;
|
||||||
import dan200.computercraft.shared.network.client.PocketComputerDeletedClientMessage;
|
import dan200.computercraft.shared.network.client.PocketComputerDeletedClientMessage;
|
||||||
import dan200.computercraft.shared.network.server.ServerNetworking;
|
import dan200.computercraft.shared.network.server.ServerNetworking;
|
||||||
import dan200.computercraft.shared.pocket.items.PocketComputerItem;
|
import dan200.computercraft.shared.pocket.items.PocketComputerItem;
|
||||||
import dan200.computercraft.shared.util.ComponentMap;
|
|
||||||
import net.minecraft.server.level.ServerPlayer;
|
import net.minecraft.server.level.ServerPlayer;
|
||||||
import net.minecraft.world.level.ChunkPos;
|
import net.minecraft.world.level.ChunkPos;
|
||||||
|
|
||||||
@ -41,10 +39,10 @@ public final class PocketServerComputer extends ServerComputer {
|
|||||||
|
|
||||||
private Set<ServerPlayer> tracking = Set.of();
|
private Set<ServerPlayer> tracking = Set.of();
|
||||||
|
|
||||||
PocketServerComputer(PocketBrain brain, PocketHolder holder, int computerID, @Nullable String label, ComputerFamily family) {
|
PocketServerComputer(PocketBrain brain, PocketHolder holder, ServerComputer.Properties properties) {
|
||||||
super(
|
super(holder.level(), holder.blockPos(), properties
|
||||||
holder.level(), holder.blockPos(), computerID, label, family, Config.pocketTermWidth, Config.pocketTermHeight,
|
.terminalSize(ConfigSpec.pocketTermWidth.get(), ConfigSpec.pocketTermHeight.get())
|
||||||
ComponentMap.builder().add(ComputerComponents.POCKET, brain).build()
|
.addComponent(ComputerComponents.POCKET, brain)
|
||||||
);
|
);
|
||||||
this.brain = brain;
|
this.brain = brain;
|
||||||
}
|
}
|
||||||
|
@ -196,7 +196,10 @@ public class PocketComputerItem extends Item implements IMedia {
|
|||||||
}
|
}
|
||||||
|
|
||||||
var computerID = NonNegativeId.getOrCreate(level.getServer(), stack, ModRegistry.DataComponents.COMPUTER_ID.get(), IDAssigner.COMPUTER);
|
var computerID = NonNegativeId.getOrCreate(level.getServer(), stack, ModRegistry.DataComponents.COMPUTER_ID.get(), IDAssigner.COMPUTER);
|
||||||
var brain = new PocketBrain(holder, computerID, getLabel(stack), getFamily(), getUpgradeWithData(stack));
|
var brain = new PocketBrain(
|
||||||
|
holder, getUpgradeWithData(stack),
|
||||||
|
ServerComputer.properties(computerID, getFamily()).label(getLabel(stack))
|
||||||
|
);
|
||||||
var computer = brain.computer();
|
var computer = brain.computer();
|
||||||
|
|
||||||
stack.set(ModRegistry.DataComponents.COMPUTER.get(), new ServerComputerReference(registry.getSessionID(), computer.register()));
|
stack.set(ModRegistry.DataComponents.COMPUTER.get(), new ServerComputerReference(registry.getSessionID(), computer.register()));
|
||||||
|
@ -24,7 +24,6 @@ import dan200.computercraft.shared.platform.PlatformHelper;
|
|||||||
import dan200.computercraft.shared.turtle.TurtleOverlay;
|
import dan200.computercraft.shared.turtle.TurtleOverlay;
|
||||||
import dan200.computercraft.shared.turtle.core.TurtleBrain;
|
import dan200.computercraft.shared.turtle.core.TurtleBrain;
|
||||||
import dan200.computercraft.shared.turtle.inventory.TurtleMenu;
|
import dan200.computercraft.shared.turtle.inventory.TurtleMenu;
|
||||||
import dan200.computercraft.shared.util.ComponentMap;
|
|
||||||
import net.minecraft.core.BlockPos;
|
import net.minecraft.core.BlockPos;
|
||||||
import net.minecraft.core.Direction;
|
import net.minecraft.core.Direction;
|
||||||
import net.minecraft.core.HolderLookup;
|
import net.minecraft.core.HolderLookup;
|
||||||
@ -80,10 +79,10 @@ public class TurtleBlockEntity extends AbstractComputerBlockEntity implements Ba
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected ServerComputer createComputer(int id) {
|
protected ServerComputer createComputer(int id) {
|
||||||
var computer = new ServerComputer(
|
var computer = new ServerComputer((ServerLevel) getLevel(), getBlockPos(), ServerComputer.properties(id, getFamily())
|
||||||
(ServerLevel) getLevel(), getBlockPos(), id, label,
|
.label(getLabel())
|
||||||
getFamily(), Config.turtleTermWidth, Config.turtleTermHeight,
|
.terminalSize(Config.TURTLE_TERM_WIDTH, Config.TURTLE_TERM_HEIGHT)
|
||||||
ComponentMap.builder().add(ComputerComponents.TURTLE, brain).build()
|
.addComponent(ComputerComponents.TURTLE, brain)
|
||||||
);
|
);
|
||||||
brain.setupComputer(computer);
|
brain.setupComputer(computer);
|
||||||
return computer;
|
return computer;
|
||||||
|
@ -44,125 +44,33 @@ public final class NBTUtil {
|
|||||||
.ifPresent(x -> destination.put(key, x));
|
.ifPresent(x -> destination.put(key, x));
|
||||||
}
|
}
|
||||||
|
|
||||||
private static @Nullable Tag toNBTTag(@Nullable Object object) {
|
|
||||||
if (object == null) return null;
|
|
||||||
if (object instanceof Boolean) return ByteTag.valueOf((byte) ((boolean) (Boolean) object ? 1 : 0));
|
|
||||||
if (object instanceof Number) return DoubleTag.valueOf(((Number) object).doubleValue());
|
|
||||||
if (object instanceof String) return StringTag.valueOf(object.toString());
|
|
||||||
if (object instanceof Map<?, ?> m) {
|
|
||||||
var nbt = new CompoundTag();
|
|
||||||
var i = 0;
|
|
||||||
for (Map.Entry<?, ?> entry : m.entrySet()) {
|
|
||||||
var key = toNBTTag(entry.getKey());
|
|
||||||
var value = toNBTTag(entry.getKey());
|
|
||||||
if (key != null && value != null) {
|
|
||||||
nbt.put("k" + i, key);
|
|
||||||
nbt.put("v" + i, value);
|
|
||||||
i++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
nbt.putInt("len", m.size());
|
|
||||||
return nbt;
|
|
||||||
}
|
|
||||||
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static @Nullable CompoundTag encodeObjects(@Nullable Object[] objects) {
|
|
||||||
if (objects == null || objects.length == 0) return null;
|
|
||||||
|
|
||||||
var nbt = new CompoundTag();
|
|
||||||
nbt.putInt("len", objects.length);
|
|
||||||
for (var i = 0; i < objects.length; i++) {
|
|
||||||
var child = toNBTTag(objects[i]);
|
|
||||||
if (child != null) nbt.put(Integer.toString(i), child);
|
|
||||||
}
|
|
||||||
return nbt;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static @Nullable Object fromNBTTag(@Nullable Tag tag) {
|
|
||||||
if (tag == null) return null;
|
|
||||||
switch (tag.getId()) {
|
|
||||||
case Tag.TAG_BYTE:
|
|
||||||
return ((ByteTag) tag).getAsByte() > 0;
|
|
||||||
case Tag.TAG_DOUBLE:
|
|
||||||
return ((DoubleTag) tag).getAsDouble();
|
|
||||||
default:
|
|
||||||
case Tag.TAG_STRING:
|
|
||||||
return tag.getAsString();
|
|
||||||
case Tag.TAG_COMPOUND: {
|
|
||||||
var c = (CompoundTag) tag;
|
|
||||||
var len = c.getInt("len");
|
|
||||||
Map<Object, Object> map = new HashMap<>(len);
|
|
||||||
for (var i = 0; i < len; i++) {
|
|
||||||
var key = fromNBTTag(c.get("k" + i));
|
|
||||||
var value = fromNBTTag(c.get("v" + i));
|
|
||||||
if (key != null && value != null) map.put(key, value);
|
|
||||||
}
|
|
||||||
return map;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static @Nullable Object toLua(@Nullable Tag tag) {
|
public static @Nullable Object toLua(@Nullable Tag tag) {
|
||||||
if (tag == null) return null;
|
if (tag == null) return null;
|
||||||
|
|
||||||
switch (tag.getId()) {
|
return switch (tag.getId()) {
|
||||||
case Tag.TAG_BYTE:
|
case Tag.TAG_BYTE, Tag.TAG_SHORT, Tag.TAG_INT, Tag.TAG_LONG -> ((NumericTag) tag).getAsLong();
|
||||||
case Tag.TAG_SHORT:
|
case Tag.TAG_FLOAT, Tag.TAG_DOUBLE -> ((NumericTag) tag).getAsDouble();
|
||||||
case Tag.TAG_INT:
|
case Tag.TAG_STRING -> tag.getAsString();
|
||||||
case Tag.TAG_LONG:
|
case Tag.TAG_COMPOUND -> {
|
||||||
return ((NumericTag) tag).getAsLong();
|
|
||||||
case Tag.TAG_FLOAT:
|
|
||||||
case Tag.TAG_DOUBLE:
|
|
||||||
return ((NumericTag) tag).getAsDouble();
|
|
||||||
case Tag.TAG_STRING: // String
|
|
||||||
return tag.getAsString();
|
|
||||||
case Tag.TAG_COMPOUND: { // Compound
|
|
||||||
var compound = (CompoundTag) tag;
|
var compound = (CompoundTag) tag;
|
||||||
Map<String, Object> map = new HashMap<>(compound.size());
|
Map<String, Object> map = new HashMap<>(compound.size());
|
||||||
for (var key : compound.getAllKeys()) {
|
for (var key : compound.getAllKeys()) {
|
||||||
var value = toLua(compound.get(key));
|
var value = toLua(compound.get(key));
|
||||||
if (value != null) map.put(key, value);
|
if (value != null) map.put(key, value);
|
||||||
}
|
}
|
||||||
return map;
|
yield map;
|
||||||
}
|
}
|
||||||
case Tag.TAG_LIST: {
|
case Tag.TAG_LIST -> ((ListTag) tag).stream().map(NBTUtil::toLua).toList();
|
||||||
var list = (ListTag) tag;
|
case Tag.TAG_BYTE_ARRAY -> {
|
||||||
List<Object> map = new ArrayList<>(list.size());
|
|
||||||
for (var value : list) map.add(toLua(value));
|
|
||||||
return map;
|
|
||||||
}
|
|
||||||
case Tag.TAG_BYTE_ARRAY: {
|
|
||||||
var array = ((ByteArrayTag) tag).getAsByteArray();
|
var array = ((ByteArrayTag) tag).getAsByteArray();
|
||||||
List<Byte> map = new ArrayList<>(array.length);
|
List<Byte> map = new ArrayList<>(array.length);
|
||||||
for (var b : array) map.add(b);
|
for (var b : array) map.add(b);
|
||||||
return map;
|
yield map;
|
||||||
}
|
}
|
||||||
case Tag.TAG_INT_ARRAY: {
|
case Tag.TAG_INT_ARRAY -> Arrays.stream(((IntArrayTag) tag).getAsIntArray()).boxed().toList();
|
||||||
var array = ((IntArrayTag) tag).getAsIntArray();
|
case Tag.TAG_LONG_ARRAY -> Arrays.stream(((LongArrayTag) tag).getAsLongArray()).boxed().toList();
|
||||||
List<Integer> map = new ArrayList<>(array.length);
|
default -> null;
|
||||||
for (var j : array) map.add(j);
|
};
|
||||||
return map;
|
|
||||||
}
|
|
||||||
|
|
||||||
default:
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static @Nullable Object[] decodeObjects(CompoundTag tag) {
|
|
||||||
var len = tag.getInt("len");
|
|
||||||
if (len <= 0) return null;
|
|
||||||
|
|
||||||
var objects = new Object[len];
|
|
||||||
for (var i = 0; i < len; i++) {
|
|
||||||
var key = Integer.toString(i);
|
|
||||||
if (tag.contains(key)) {
|
|
||||||
objects[i] = fromNBTTag(tag.get(key));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return objects;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Nullable
|
@Nullable
|
||||||
|
Before Width: | Height: | Size: 145 B After Width: | Height: | Size: 145 B |
Before Width: | Height: | Size: 144 B After Width: | Height: | Size: 144 B |
Before Width: | Height: | Size: 145 B After Width: | Height: | Size: 145 B |
Before Width: | Height: | Size: 145 B After Width: | Height: | Size: 145 B |
Before Width: | Height: | Size: 146 B After Width: | Height: | Size: 146 B |
Before Width: | Height: | Size: 146 B After Width: | Height: | Size: 146 B |
After Width: | Height: | Size: 461 B |
After Width: | Height: | Size: 476 B |
Before Width: | Height: | Size: 1.4 KiB After Width: | Height: | Size: 1.1 KiB |
Before Width: | Height: | Size: 1.4 KiB After Width: | Height: | Size: 1.1 KiB |
@ -0,0 +1,51 @@
|
|||||||
|
// SPDX-FileCopyrightText: 2025 The CC: Tweaked Developers
|
||||||
|
//
|
||||||
|
// SPDX-License-Identifier: MPL-2.0
|
||||||
|
|
||||||
|
package dan200.computercraft.mixin.gametest;
|
||||||
|
|
||||||
|
import com.mojang.datafixers.DataFixer;
|
||||||
|
import dan200.computercraft.gametest.core.TestHooks;
|
||||||
|
import net.minecraft.gametest.framework.GameTestServer;
|
||||||
|
import net.minecraft.server.MinecraftServer;
|
||||||
|
import net.minecraft.server.Services;
|
||||||
|
import net.minecraft.server.WorldStem;
|
||||||
|
import net.minecraft.server.level.progress.ChunkProgressListenerFactory;
|
||||||
|
import net.minecraft.server.packs.repository.PackRepository;
|
||||||
|
import net.minecraft.world.level.storage.LevelStorageSource;
|
||||||
|
import org.spongepowered.asm.mixin.Mixin;
|
||||||
|
import org.spongepowered.asm.mixin.Overwrite;
|
||||||
|
import org.spongepowered.asm.mixin.Shadow;
|
||||||
|
|
||||||
|
import java.net.Proxy;
|
||||||
|
import java.util.concurrent.locks.LockSupport;
|
||||||
|
|
||||||
|
@Mixin(GameTestServer.class)
|
||||||
|
abstract class GameTestServerMixin extends MinecraftServer {
|
||||||
|
GameTestServerMixin(Thread serverThread, LevelStorageSource.LevelStorageAccess storageSource, PackRepository packRepository, WorldStem worldStem, Proxy proxy, DataFixer fixerUpper, Services services, ChunkProgressListenerFactory progressListenerFactory) {
|
||||||
|
super(serverThread, storageSource, packRepository, worldStem, proxy, fixerUpper, services, progressListenerFactory);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Overwrite {@link GameTestServer#waitUntilNextTick()} to wait for all computers to finish executing.
|
||||||
|
* <p>
|
||||||
|
* This is a little dangerous (breaks async behaviour of computers), but it forces tests to be deterministic.
|
||||||
|
*
|
||||||
|
* @reason See above. This is only in the test mod, so no risk of collision.
|
||||||
|
* @author SquidDev.
|
||||||
|
*/
|
||||||
|
@Overwrite
|
||||||
|
@Override
|
||||||
|
public void waitUntilNextTick() {
|
||||||
|
while (true) {
|
||||||
|
runAllTasks();
|
||||||
|
if (!haveTestsStarted() || TestHooks.areComputersIdle(this)) break;
|
||||||
|
LockSupport.parkNanos(100_000);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Shadow
|
||||||
|
private boolean haveTestsStarted() {
|
||||||
|
throw new AssertionError("Stub.");
|
||||||
|
}
|
||||||
|
}
|
@ -5,6 +5,8 @@
|
|||||||
package dan200.computercraft.gametest.core
|
package dan200.computercraft.gametest.core
|
||||||
|
|
||||||
import dan200.computercraft.api.ComputerCraftAPI
|
import dan200.computercraft.api.ComputerCraftAPI
|
||||||
|
import dan200.computercraft.core.ComputerContext
|
||||||
|
import dan200.computercraft.core.computer.computerthread.ComputerThread
|
||||||
import dan200.computercraft.gametest.*
|
import dan200.computercraft.gametest.*
|
||||||
import dan200.computercraft.gametest.api.ClientGameTest
|
import dan200.computercraft.gametest.api.ClientGameTest
|
||||||
import dan200.computercraft.gametest.api.TestTags
|
import dan200.computercraft.gametest.api.TestTags
|
||||||
@ -24,6 +26,8 @@ import net.minecraft.world.phys.Vec3
|
|||||||
import org.slf4j.Logger
|
import org.slf4j.Logger
|
||||||
import org.slf4j.LoggerFactory
|
import org.slf4j.LoggerFactory
|
||||||
import java.io.File
|
import java.io.File
|
||||||
|
import java.lang.invoke.MethodHandle
|
||||||
|
import java.lang.invoke.MethodHandles
|
||||||
import java.lang.reflect.InvocationTargetException
|
import java.lang.reflect.InvocationTargetException
|
||||||
import java.lang.reflect.Method
|
import java.lang.reflect.Method
|
||||||
import java.lang.reflect.Modifier
|
import java.lang.reflect.Modifier
|
||||||
@ -91,6 +95,9 @@ object TestHooks {
|
|||||||
CCTestCommand.importFiles(server)
|
CCTestCommand.importFiles(server)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@JvmStatic
|
||||||
|
fun areComputersIdle(server: MinecraftServer) = ComputerThreadReflection.isFullyIdle(ServerContext.get(server))
|
||||||
|
|
||||||
private val testClasses = listOf(
|
private val testClasses = listOf(
|
||||||
Computer_Test::class.java,
|
Computer_Test::class.java,
|
||||||
CraftOs_Test::class.java,
|
CraftOs_Test::class.java,
|
||||||
@ -128,14 +135,6 @@ object TestHooks {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private val isCi = System.getenv("CI") != null
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Adjust the timeout of a test. This makes it 1.5 times longer when run under CI, as CI servers are less powerful
|
|
||||||
* than our own.
|
|
||||||
*/
|
|
||||||
private fun adjustTimeout(timeout: Int): Int = if (isCi) timeout + (timeout / 2) else timeout
|
|
||||||
|
|
||||||
private fun registerTest(testClass: Class<*>, method: Method, fallbackRegister: Consumer<Method>) {
|
private fun registerTest(testClass: Class<*>, method: Method, fallbackRegister: Consumer<Method>) {
|
||||||
val className = testClass.simpleName.lowercase()
|
val className = testClass.simpleName.lowercase()
|
||||||
val testName = className + "." + method.name.lowercase()
|
val testName = className + "." + method.name.lowercase()
|
||||||
@ -147,7 +146,7 @@ object TestHooks {
|
|||||||
TestFunction(
|
TestFunction(
|
||||||
testInfo.batch, testName, testInfo.template.ifEmpty { testName },
|
testInfo.batch, testName, testInfo.template.ifEmpty { testName },
|
||||||
StructureUtils.getRotationForRotationSteps(testInfo.rotationSteps),
|
StructureUtils.getRotationForRotationSteps(testInfo.rotationSteps),
|
||||||
adjustTimeout(testInfo.timeoutTicks),
|
testInfo.timeoutTicks,
|
||||||
testInfo.setupTicks,
|
testInfo.setupTicks,
|
||||||
testInfo.required, testInfo.manualOnly,
|
testInfo.required, testInfo.manualOnly,
|
||||||
testInfo.attempts,
|
testInfo.attempts,
|
||||||
@ -167,7 +166,7 @@ object TestHooks {
|
|||||||
testName,
|
testName,
|
||||||
testName,
|
testName,
|
||||||
testInfo.template.ifEmpty { testName },
|
testInfo.template.ifEmpty { testName },
|
||||||
adjustTimeout(testInfo.timeoutTicks),
|
testInfo.timeoutTicks,
|
||||||
0,
|
0,
|
||||||
true,
|
true,
|
||||||
) { value -> safeInvoke(method, value) },
|
) { value -> safeInvoke(method, value) },
|
||||||
@ -215,3 +214,31 @@ object TestHooks {
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Nasty reflection to determine if computers are fully idle.
|
||||||
|
*
|
||||||
|
* This is horribly nasty, and should not be used as a model for any production code!
|
||||||
|
*
|
||||||
|
* @see [ComputerThread.isFullyIdle]
|
||||||
|
* @see [dan200.computercraft.mixin.gametest.GameTestServerMixin]
|
||||||
|
*/
|
||||||
|
private object ComputerThreadReflection {
|
||||||
|
private val lookup = MethodHandles.lookup()
|
||||||
|
|
||||||
|
@JvmField
|
||||||
|
val computerContext: MethodHandle = lookup.unreflectGetter(
|
||||||
|
ServerContext::class.java.getDeclaredField("context").also { it.isAccessible = true },
|
||||||
|
)
|
||||||
|
|
||||||
|
@JvmField
|
||||||
|
val isFullyIdle: MethodHandle = lookup.unreflect(
|
||||||
|
ComputerThread::class.java.getDeclaredMethod("isFullyIdle").also { it.isAccessible = true },
|
||||||
|
)
|
||||||
|
|
||||||
|
fun isFullyIdle(context: ServerContext): Boolean {
|
||||||
|
val computerContext = computerContext.invokeExact(context) as ComputerContext
|
||||||
|
val computerThread = computerContext.computerScheduler() as ComputerThread
|
||||||
|
return isFullyIdle.invokeExact(computerThread) as Boolean
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -35,7 +35,7 @@ class MultiTestReporter(private val reporters: List<TestReporter>) : TestReporte
|
|||||||
* Reports tests to a JUnit XML file. This is equivalent to [JUnitLikeTestReporter], except it ensures the destination
|
* Reports tests to a JUnit XML file. This is equivalent to [JUnitLikeTestReporter], except it ensures the destination
|
||||||
* directory exists.
|
* directory exists.
|
||||||
*/
|
*/
|
||||||
class JunitTestReporter constructor(destination: File) : JUnitLikeTestReporter(destination) {
|
class JunitTestReporter(destination: File) : JUnitLikeTestReporter(destination) {
|
||||||
override fun save(file: File) {
|
override fun save(file: File) {
|
||||||
try {
|
try {
|
||||||
Files.createDirectories(file.toPath().parent)
|
Files.createDirectories(file.toPath().parent)
|
||||||
|
@ -11,6 +11,7 @@
|
|||||||
"GameTestInfoAccessor",
|
"GameTestInfoAccessor",
|
||||||
"GameTestSequenceAccessor",
|
"GameTestSequenceAccessor",
|
||||||
"GameTestSequenceMixin",
|
"GameTestSequenceMixin",
|
||||||
|
"GameTestServerMixin",
|
||||||
"StructureTemplateManagerMixin",
|
"StructureTemplateManagerMixin",
|
||||||
"TestCommandAccessor"
|
"TestCommandAccessor"
|
||||||
],
|
],
|
||||||
|
@ -40,6 +40,8 @@ dependencies {
|
|||||||
}
|
}
|
||||||
|
|
||||||
tasks.processResources {
|
tasks.processResources {
|
||||||
|
inputs.property("gitHash", cct.gitHash)
|
||||||
|
|
||||||
var props = mapOf("gitContributors" to cct.gitContributors.get().joinToString("\n"))
|
var props = mapOf("gitContributors" to cct.gitContributors.get().joinToString("\n"))
|
||||||
filesMatching("data/computercraft/lua/rom/help/credits.md") { expand(props) }
|
filesMatching("data/computercraft/lua/rom/help/credits.md") { expand(props) }
|
||||||
}
|
}
|
||||||
|
@ -31,7 +31,7 @@ import java.util.concurrent.atomic.AtomicLong;
|
|||||||
* <li>Passes main thread tasks to the {@link MainThreadScheduler.Executor}.</li>
|
* <li>Passes main thread tasks to the {@link MainThreadScheduler.Executor}.</li>
|
||||||
* </ul>
|
* </ul>
|
||||||
*/
|
*/
|
||||||
public class Computer {
|
public class Computer implements ComputerEvents.Receiver {
|
||||||
private static final int START_DELAY = 50;
|
private static final int START_DELAY = 50;
|
||||||
|
|
||||||
// Various properties of the computer
|
// Various properties of the computer
|
||||||
@ -114,6 +114,7 @@ public class Computer {
|
|||||||
executor.queueStop(false, true);
|
executor.queueStop(false, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
public void queueEvent(String event, @Nullable Object[] args) {
|
public void queueEvent(String event, @Nullable Object[] args) {
|
||||||
executor.queueEvent(event, args);
|
executor.queueEvent(event, args);
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,72 @@
|
|||||||
|
// SPDX-FileCopyrightText: 2025 The CC: Tweaked Developers
|
||||||
|
//
|
||||||
|
// SPDX-License-Identifier: MPL-2.0
|
||||||
|
|
||||||
|
package dan200.computercraft.core.computer;
|
||||||
|
|
||||||
|
import dan200.computercraft.core.util.StringUtil;
|
||||||
|
|
||||||
|
import javax.annotation.Nullable;
|
||||||
|
import java.nio.ByteBuffer;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Built-in events that can be queued on a computer.
|
||||||
|
*/
|
||||||
|
public final class ComputerEvents {
|
||||||
|
private ComputerEvents() {
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void keyDown(Receiver receiver, int key, boolean repeat) {
|
||||||
|
receiver.queueEvent("key", new Object[]{ key, repeat });
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void keyUp(Receiver receiver, int key) {
|
||||||
|
receiver.queueEvent("key_up", new Object[]{ key });
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Type a character on the computer.
|
||||||
|
*
|
||||||
|
* @param receiver The computer to queue the event on.
|
||||||
|
* @param chr The character to type.
|
||||||
|
* @see StringUtil#isTypableChar(byte)
|
||||||
|
*/
|
||||||
|
public static void charTyped(Receiver receiver, byte chr) {
|
||||||
|
receiver.queueEvent("char", new Object[]{ new byte[]{ chr } });
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Paste a string.
|
||||||
|
*
|
||||||
|
* @param receiver The computer to queue the event on.
|
||||||
|
* @param contents The string to paste.
|
||||||
|
* @see StringUtil#getClipboardString(String)
|
||||||
|
*/
|
||||||
|
public static void paste(Receiver receiver, ByteBuffer contents) {
|
||||||
|
receiver.queueEvent("paste", new Object[]{ contents });
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void mouseClick(Receiver receiver, int button, int x, int y) {
|
||||||
|
receiver.queueEvent("mouse_click", new Object[]{ button, x, y });
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void mouseUp(Receiver receiver, int button, int x, int y) {
|
||||||
|
receiver.queueEvent("mouse_up", new Object[]{ button, x, y });
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void mouseDrag(Receiver receiver, int button, int x, int y) {
|
||||||
|
receiver.queueEvent("mouse_drag", new Object[]{ button, x, y });
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void mouseScroll(Receiver receiver, int direction, int x, int y) {
|
||||||
|
receiver.queueEvent("mouse_scroll", new Object[]{ direction, x, y });
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An object that can receive computer events.
|
||||||
|
*/
|
||||||
|
@FunctionalInterface
|
||||||
|
public interface Receiver {
|
||||||
|
void queueEvent(String event, @Nullable Object[] arguments);
|
||||||
|
}
|
||||||
|
}
|
@ -433,6 +433,16 @@ public final class ComputerThread implements ComputerScheduler {
|
|||||||
return computerQueueSize() > idleWorkers.get();
|
return computerQueueSize() > idleWorkers.get();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Determine if no work is queued, and all workers are idle.
|
||||||
|
*
|
||||||
|
* @return If the threads are fully idle.
|
||||||
|
*/
|
||||||
|
@VisibleForTesting
|
||||||
|
boolean isFullyIdle() {
|
||||||
|
return computerQueueSize() == 0 && idleWorkers.get() >= workerCount();
|
||||||
|
}
|
||||||
|
|
||||||
private void workerFinished(WorkerThread worker) {
|
private void workerFinished(WorkerThread worker) {
|
||||||
// We should only shut down a worker once! This should only happen if we fail to abort a worker and then the
|
// We should only shut down a worker once! This should only happen if we fail to abort a worker and then the
|
||||||
// worker finishes normally.
|
// worker finishes normally.
|
||||||
|
@ -4,52 +4,116 @@
|
|||||||
|
|
||||||
package dan200.computercraft.core.util;
|
package dan200.computercraft.core.util;
|
||||||
|
|
||||||
|
import dan200.computercraft.core.computer.ComputerEvents;
|
||||||
|
|
||||||
|
import java.nio.ByteBuffer;
|
||||||
|
|
||||||
public final class StringUtil {
|
public final class StringUtil {
|
||||||
|
public static final int MAX_PASTE_LENGTH = 512;
|
||||||
|
|
||||||
private StringUtil() {
|
private StringUtil() {
|
||||||
}
|
}
|
||||||
|
|
||||||
private static boolean isAllowed(char c) {
|
/**
|
||||||
return (c >= ' ' && c <= '~') || (c >= 161 && c <= 172) || (c >= 174 && c <= 255);
|
* Convert a Unicode character to a terminal one.
|
||||||
}
|
*
|
||||||
|
* @param chr The Unicode character.
|
||||||
private static String removeSpecialCharacters(String text, int length) {
|
* @return The terminal character.
|
||||||
var builder = new StringBuilder(length);
|
*/
|
||||||
for (var i = 0; i < length; i++) {
|
public static byte unicodeToTerminal(int chr) {
|
||||||
var c = text.charAt(i);
|
// ASCII and latin1 map to themselves
|
||||||
builder.append(isAllowed(c) ? c : '?');
|
if (chr == 0 || chr == '\t' || chr == '\n' || chr == '\r' || (chr >= ' ' && chr <= '~') || (chr >= 160 && chr <= 255)) {
|
||||||
|
return (byte) chr;
|
||||||
}
|
}
|
||||||
|
|
||||||
return builder.toString();
|
// Teletext block mosaics are *fairly* contiguous.
|
||||||
|
if (chr >= 0x1FB00 && chr <= 0x1FB13) return (byte) (chr + (129 - 0x1fb00));
|
||||||
|
if (chr >= 0x1FB14 && chr <= 0x1FB1D) return (byte) (chr + (150 - 0x1fb14));
|
||||||
|
|
||||||
|
// Everything else is just a manual lookup. For now, we just use a big switch statement, which we spin into a
|
||||||
|
// separate function to hopefully avoid inlining it here.
|
||||||
|
return unicodeToCraftOsFallback(chr);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static String normaliseLabel(String text) {
|
private static byte unicodeToCraftOsFallback(int c) {
|
||||||
return removeSpecialCharacters(text, Math.min(32, text.length()));
|
return switch (c) {
|
||||||
|
case 0x263A -> 1;
|
||||||
|
case 0x263B -> 2;
|
||||||
|
case 0x2665 -> 3;
|
||||||
|
case 0x2666 -> 4;
|
||||||
|
case 0x2663 -> 5;
|
||||||
|
case 0x2660 -> 6;
|
||||||
|
case 0x2022 -> 7;
|
||||||
|
case 0x25D8 -> 8;
|
||||||
|
case 0x2642 -> 11;
|
||||||
|
case 0x2640 -> 12;
|
||||||
|
case 0x266A -> 14;
|
||||||
|
case 0x266B -> 15;
|
||||||
|
case 0x25BA -> 16;
|
||||||
|
case 0x25C4 -> 17;
|
||||||
|
case 0x2195 -> 18;
|
||||||
|
case 0x203C -> 19;
|
||||||
|
case 0x25AC -> 22;
|
||||||
|
case 0x21A8 -> 23;
|
||||||
|
case 0x2191 -> 24;
|
||||||
|
case 0x2193 -> 25;
|
||||||
|
case 0x2192 -> 26;
|
||||||
|
case 0x2190 -> 27;
|
||||||
|
case 0x221F -> 28;
|
||||||
|
case 0x2194 -> 29;
|
||||||
|
case 0x25B2 -> 30;
|
||||||
|
case 0x25BC -> 31;
|
||||||
|
case 0x1FB99 -> 127;
|
||||||
|
case 0x258C -> (byte) 149;
|
||||||
|
default -> '?';
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Normalise a string from the clipboard, suitable for pasting into a computer.
|
* Check if a character is capable of being input and passed to a {@linkplain ComputerEvents#charTyped(ComputerEvents.Receiver, byte)
|
||||||
|
* "char" event}.
|
||||||
|
*
|
||||||
|
* @param chr The character to check.
|
||||||
|
* @return Whether this character can be typed.
|
||||||
|
*/
|
||||||
|
public static boolean isTypableChar(byte chr) {
|
||||||
|
return chr != 0 && chr != '\r' && chr != '\n';
|
||||||
|
}
|
||||||
|
|
||||||
|
private static boolean isAllowedInLabel(char c) {
|
||||||
|
// Limit to ASCII and latin1, excluding '§' (Minecraft's formatting character).
|
||||||
|
return (c >= ' ' && c <= '~') || (c >= 161 && c <= 255 && c != 167);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String normaliseLabel(String text) {
|
||||||
|
var length = Math.min(32, text.length());
|
||||||
|
var builder = new StringBuilder(length);
|
||||||
|
for (var i = 0; i < length; i++) {
|
||||||
|
var c = text.charAt(i);
|
||||||
|
builder.append(isAllowedInLabel(c) ? c : '?');
|
||||||
|
}
|
||||||
|
return builder.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Convert a Java string to a Lua one (using the terminal charset), suitable for pasting into a computer.
|
||||||
* <p>
|
* <p>
|
||||||
* This removes special characters and strips to the first line of text.
|
* This removes special characters and strips to the first line of text.
|
||||||
*
|
*
|
||||||
* @param clipboard The text from the clipboard.
|
* @param clipboard The text from the clipboard.
|
||||||
* @return The normalised clipboard text.
|
* @return The encoded clipboard text.
|
||||||
*/
|
*/
|
||||||
public static String normaliseClipboardString(String clipboard) {
|
public static ByteBuffer getClipboardString(String clipboard) {
|
||||||
// Clip to the first occurrence of \r or \n
|
var output = new byte[Math.min(MAX_PASTE_LENGTH, clipboard.length())];
|
||||||
var newLineIndex1 = clipboard.indexOf('\r');
|
var idx = 0;
|
||||||
var newLineIndex2 = clipboard.indexOf('\n');
|
|
||||||
|
|
||||||
int length;
|
var iterator = clipboard.codePoints().iterator();
|
||||||
if (newLineIndex1 >= 0 && newLineIndex2 >= 0) {
|
while (iterator.hasNext() && idx <= output.length) {
|
||||||
length = Math.min(newLineIndex1, newLineIndex2);
|
var chr = unicodeToTerminal(iterator.next());
|
||||||
} else if (newLineIndex1 >= 0) {
|
if (!isTypableChar(chr)) break;
|
||||||
length = newLineIndex1;
|
output[idx++] = chr;
|
||||||
} else if (newLineIndex2 >= 0) {
|
|
||||||
length = newLineIndex2;
|
|
||||||
} else {
|
|
||||||
length = clipboard.length();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return removeSpecialCharacters(clipboard, Math.min(length, 512));
|
return ByteBuffer.wrap(output, 0, idx).asReadOnlyBuffer();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -221,6 +221,8 @@ loom {
|
|||||||
}
|
}
|
||||||
|
|
||||||
tasks.processResources {
|
tasks.processResources {
|
||||||
|
inputs.property("modVersion", modVersion)
|
||||||
|
|
||||||
var props = mapOf("version" to modVersion)
|
var props = mapOf("version" to modVersion)
|
||||||
|
|
||||||
filesMatching("fabric.mod.json") { expand(props) }
|
filesMatching("fabric.mod.json") { expand(props) }
|
||||||
|
@ -198,6 +198,9 @@ dependencies {
|
|||||||
// Compile tasks
|
// Compile tasks
|
||||||
|
|
||||||
tasks.processResources {
|
tasks.processResources {
|
||||||
|
inputs.property("modVersion", modVersion)
|
||||||
|
inputs.property("neoVersion", libs.versions.neoForge)
|
||||||
|
|
||||||
var props = mapOf(
|
var props = mapOf(
|
||||||
"neoVersion" to libs.versions.neoForge.get(),
|
"neoVersion" to libs.versions.neoForge.get(),
|
||||||
"file" to mapOf("jarVersion" to modVersion),
|
"file" to mapOf("jarVersion" to modVersion),
|
||||||
|
@ -8,6 +8,7 @@ import dan200.computercraft.core.apis.handles.ArrayByteChannel;
|
|||||||
import dan200.computercraft.core.apis.transfer.TransferredFile;
|
import dan200.computercraft.core.apis.transfer.TransferredFile;
|
||||||
import dan200.computercraft.core.apis.transfer.TransferredFiles;
|
import dan200.computercraft.core.apis.transfer.TransferredFiles;
|
||||||
import dan200.computercraft.core.computer.Computer;
|
import dan200.computercraft.core.computer.Computer;
|
||||||
|
import dan200.computercraft.core.computer.ComputerEvents;
|
||||||
import dan200.computercraft.core.util.StringUtil;
|
import dan200.computercraft.core.util.StringUtil;
|
||||||
import org.lwjgl.glfw.GLFW;
|
import org.lwjgl.glfw.GLFW;
|
||||||
import org.lwjgl.glfw.GLFWDropCallback;
|
import org.lwjgl.glfw.GLFWDropCallback;
|
||||||
@ -49,10 +50,8 @@ public class InputState {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public void onCharEvent(int codepoint) {
|
public void onCharEvent(int codepoint) {
|
||||||
if (codepoint >= 32 && codepoint <= 126 || codepoint >= 160 && codepoint <= 255) {
|
var terminalChar = StringUtil.unicodeToTerminal(codepoint);
|
||||||
// Queue the char event for any printable chars in byte range
|
if (StringUtil.isTypableChar(terminalChar)) ComputerEvents.charTyped(computer, terminalChar);
|
||||||
computer.queueEvent("char", new Object[]{ Character.toString(codepoint) });
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void onKeyEvent(long window, int key, int action, int modifiers) {
|
public void onKeyEvent(long window, int key, int action, int modifiers) {
|
||||||
@ -68,8 +67,8 @@ public class InputState {
|
|||||||
if (key == GLFW.GLFW_KEY_V && modifiers == GLFW.GLFW_MOD_CONTROL) {
|
if (key == GLFW.GLFW_KEY_V && modifiers == GLFW.GLFW_MOD_CONTROL) {
|
||||||
var string = GLFW.glfwGetClipboardString(window);
|
var string = GLFW.glfwGetClipboardString(window);
|
||||||
if (string != null) {
|
if (string != null) {
|
||||||
var clipboard = StringUtil.normaliseClipboardString(string);
|
var clipboard = StringUtil.getClipboardString(string);
|
||||||
if (!clipboard.isEmpty()) computer.queueEvent("paste", new Object[]{ clipboard });
|
if (clipboard.remaining() > 0) ComputerEvents.paste(computer, clipboard);
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -92,7 +91,7 @@ public class InputState {
|
|||||||
// Queue the "key" event and add to the down set
|
// Queue the "key" event and add to the down set
|
||||||
var repeat = keysDown.get(key);
|
var repeat = keysDown.get(key);
|
||||||
keysDown.set(key);
|
keysDown.set(key);
|
||||||
computer.queueEvent("key", new Object[]{ key, repeat });
|
ComputerEvents.keyDown(computer, key, repeat);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -100,7 +99,7 @@ public class InputState {
|
|||||||
// Queue the "key_up" event and remove from the down set
|
// Queue the "key_up" event and remove from the down set
|
||||||
if (key >= 0 && keysDown.get(key)) {
|
if (key >= 0 && keysDown.get(key)) {
|
||||||
keysDown.set(key, false);
|
keysDown.set(key, false);
|
||||||
computer.queueEvent("key_up", new Object[]{ key });
|
ComputerEvents.keyUp(computer, key);
|
||||||
}
|
}
|
||||||
|
|
||||||
switch (key) {
|
switch (key) {
|
||||||
@ -115,12 +114,12 @@ public class InputState {
|
|||||||
public void onMouseClick(int button, int action) {
|
public void onMouseClick(int button, int action) {
|
||||||
switch (action) {
|
switch (action) {
|
||||||
case GLFW.GLFW_PRESS -> {
|
case GLFW.GLFW_PRESS -> {
|
||||||
computer.queueEvent("mouse_click", new Object[]{ button + 1, lastMouseX + 1, lastMouseY + 1 });
|
ComputerEvents.mouseClick(computer, button + 1, lastMouseX + 1, lastMouseY + 1);
|
||||||
lastMouseButton = button;
|
lastMouseButton = button;
|
||||||
}
|
}
|
||||||
case GLFW.GLFW_RELEASE -> {
|
case GLFW.GLFW_RELEASE -> {
|
||||||
if (button == lastMouseButton) {
|
if (button == lastMouseButton) {
|
||||||
computer.queueEvent("mouse_click", new Object[]{ button + 1, lastMouseX + 1, lastMouseY + 1 });
|
ComputerEvents.mouseUp(computer, button + 1, lastMouseX + 1, lastMouseY + 1);
|
||||||
lastMouseButton = -1;
|
lastMouseButton = -1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -133,13 +132,13 @@ public class InputState {
|
|||||||
lastMouseX = mouseX;
|
lastMouseX = mouseX;
|
||||||
lastMouseY = mouseY;
|
lastMouseY = mouseY;
|
||||||
if (lastMouseButton != -1) {
|
if (lastMouseButton != -1) {
|
||||||
computer.queueEvent("mouse_drag", new Object[]{ lastMouseButton + 1, mouseX + 1, mouseY + 1 });
|
ComputerEvents.mouseDrag(computer, lastMouseButton + 1, mouseX + 1, mouseY + 1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void onMouseScroll(double yOffset) {
|
public void onMouseScroll(double yOffset) {
|
||||||
if (yOffset != 0) {
|
if (yOffset != 0) {
|
||||||
computer.queueEvent("mouse_scroll", new Object[]{ yOffset < 0 ? 1 : -1, lastMouseX + 1, lastMouseY + 1 });
|
ComputerEvents.mouseScroll(computer, yOffset < 0 ? 1 : -1, lastMouseX + 1, lastMouseY + 1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|