mirror of
https://github.com/SquidDev-CC/CC-Tweaked
synced 2025-01-10 01:10:30 +00:00
Merge branch 'mc-1.20.x' into mc-1.20.y
This commit is contained in:
commit
22bd5309ba
12
.github/workflows/main-ci.yml
vendored
12
.github/workflows/main-ci.yml
vendored
@ -9,16 +9,16 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: 📥 Clone repository
|
||||
uses: actions/checkout@v3
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: 📥 Set up Java
|
||||
uses: actions/setup-java@v3
|
||||
uses: actions/setup-java@v4
|
||||
with:
|
||||
java-version: 17
|
||||
distribution: 'temurin'
|
||||
|
||||
- name: 📥 Setup Gradle
|
||||
uses: gradle/gradle-build-action@v2
|
||||
uses: gradle/actions/setup-gradle@v3
|
||||
with:
|
||||
cache-read-only: ${{ !startsWith(github.ref, 'refs/heads/mc-') }}
|
||||
|
||||
@ -82,16 +82,16 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Clone repository
|
||||
uses: actions/checkout@v3
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Set up Java
|
||||
uses: actions/setup-java@v3
|
||||
uses: actions/setup-java@v4
|
||||
with:
|
||||
java-version: 17
|
||||
distribution: 'temurin'
|
||||
|
||||
- name: Setup Gradle
|
||||
uses: gradle/gradle-build-action@v2
|
||||
uses: gradle/actions/setup-gradle@v3
|
||||
with:
|
||||
cache-read-only: ${{ !startsWith(github.ref, 'refs/heads/mc-') }}
|
||||
|
||||
|
6
.github/workflows/make-doc.yml
vendored
6
.github/workflows/make-doc.yml
vendored
@ -13,16 +13,16 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Clone repository
|
||||
uses: actions/checkout@v3
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Set up Java
|
||||
uses: actions/setup-java@v1
|
||||
uses: actions/setup-java@v4
|
||||
with:
|
||||
java-version: 17
|
||||
distribution: 'temurin'
|
||||
|
||||
- name: Setup Gradle
|
||||
uses: gradle/gradle-build-action@v2
|
||||
uses: gradle/actions/setup-gradle@v3
|
||||
with:
|
||||
cache-read-only: ${{ !startsWith(github.ref, 'refs/heads/mc-') }}
|
||||
|
||||
|
@ -10,7 +10,7 @@ kotlin.jvm.target.validation.mode=error
|
||||
|
||||
# Mod properties
|
||||
isUnstable=true
|
||||
modVersion=1.110.0
|
||||
modVersion=1.110.2
|
||||
|
||||
# Minecraft properties: We want to configure this here so we can read it in settings.gradle
|
||||
mcVersion=1.20.4
|
||||
|
@ -63,11 +63,11 @@ fabric-loom = "1.5.7"
|
||||
githubRelease = "2.5.2"
|
||||
gradleVersions = "0.50.0"
|
||||
ideaExt = "1.1.7"
|
||||
illuaminate = "0.1.0-69-gf294ab2"
|
||||
illuaminate = "0.1.0-71-g378d86e"
|
||||
lwjgl = "3.3.3"
|
||||
minotaur = "2.8.7"
|
||||
neoGradle = "7.0.100"
|
||||
nullAway = "0.9.9"
|
||||
nullAway = "0.10.25"
|
||||
spotless = "6.23.3"
|
||||
taskTree = "2.1.1"
|
||||
teavm = "0.10.0-SQUID.3"
|
||||
|
@ -9,6 +9,7 @@ import dan200.computercraft.client.gui.widgets.DynamicImageButton;
|
||||
import dan200.computercraft.client.gui.widgets.TerminalWidget;
|
||||
import dan200.computercraft.client.network.ClientNetworking;
|
||||
import dan200.computercraft.core.terminal.Terminal;
|
||||
import dan200.computercraft.core.util.Nullability;
|
||||
import dan200.computercraft.shared.computer.core.ComputerFamily;
|
||||
import dan200.computercraft.shared.computer.core.InputHandler;
|
||||
import dan200.computercraft.shared.computer.inventory.AbstractComputerMenu;
|
||||
@ -18,6 +19,7 @@ import dan200.computercraft.shared.config.Config;
|
||||
import dan200.computercraft.shared.network.server.UploadFileMessage;
|
||||
import net.minecraft.ChatFormatting;
|
||||
import net.minecraft.Util;
|
||||
import net.minecraft.client.Minecraft;
|
||||
import net.minecraft.client.gui.GuiGraphics;
|
||||
import net.minecraft.client.gui.components.events.GuiEventListener;
|
||||
import net.minecraft.client.gui.screens.inventory.AbstractContainerScreen;
|
||||
@ -96,8 +98,8 @@ public abstract class AbstractComputerScreen<T extends AbstractComputerMenu> ext
|
||||
getTerminal().update();
|
||||
|
||||
if (uploadNagDeadline != Long.MAX_VALUE && Util.getNanos() >= uploadNagDeadline) {
|
||||
new ItemToast(minecraft, displayStack, NO_RESPONSE_TITLE, NO_RESPONSE_MSG, ItemToast.TRANSFER_NO_RESPONSE_TOKEN)
|
||||
.showOrReplace(minecraft.getToasts());
|
||||
new ItemToast(minecraft(), displayStack, NO_RESPONSE_TITLE, NO_RESPONSE_MSG, ItemToast.TRANSFER_NO_RESPONSE_TOKEN)
|
||||
.showOrReplace(minecraft().getToasts());
|
||||
uploadNagDeadline = Long.MAX_VALUE;
|
||||
}
|
||||
}
|
||||
@ -206,7 +208,7 @@ public abstract class AbstractComputerScreen<T extends AbstractComputerMenu> ext
|
||||
return;
|
||||
}
|
||||
|
||||
if (toUpload.size() > 0) UploadFileMessage.send(menu, toUpload, ClientNetworking::sendToServer);
|
||||
if (!toUpload.isEmpty()) UploadFileMessage.send(menu, toUpload, ClientNetworking::sendToServer);
|
||||
}
|
||||
|
||||
public void uploadResult(UploadResult result, @Nullable Component message) {
|
||||
@ -222,9 +224,13 @@ public abstract class AbstractComputerScreen<T extends AbstractComputerMenu> ext
|
||||
}
|
||||
|
||||
private void alert(Component title, Component message) {
|
||||
OptionScreen.show(minecraft, title, message,
|
||||
List.of(OptionScreen.newButton(OK, b -> minecraft.setScreen(this))),
|
||||
() -> minecraft.setScreen(this)
|
||||
OptionScreen.show(minecraft(), title, message,
|
||||
List.of(OptionScreen.newButton(OK, b -> minecraft().setScreen(this))),
|
||||
() -> minecraft().setScreen(this)
|
||||
);
|
||||
}
|
||||
|
||||
private Minecraft minecraft() {
|
||||
return Nullability.assertNonNull(minecraft);
|
||||
}
|
||||
}
|
||||
|
@ -6,8 +6,10 @@ package dan200.computercraft.client.gui;
|
||||
|
||||
import dan200.computercraft.client.gui.widgets.TerminalWidget;
|
||||
import dan200.computercraft.core.terminal.Terminal;
|
||||
import dan200.computercraft.core.util.Nullability;
|
||||
import dan200.computercraft.shared.computer.inventory.AbstractComputerMenu;
|
||||
import net.minecraft.client.KeyMapping;
|
||||
import net.minecraft.client.Minecraft;
|
||||
import net.minecraft.client.gui.GuiGraphics;
|
||||
import net.minecraft.client.gui.screens.Screen;
|
||||
import net.minecraft.client.gui.screens.inventory.MenuAccess;
|
||||
@ -16,6 +18,7 @@ import net.minecraft.world.entity.player.Inventory;
|
||||
import org.lwjgl.glfw.GLFW;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
import java.util.Objects;
|
||||
|
||||
import static dan200.computercraft.core.util.Nullability.assertNonNull;
|
||||
|
||||
@ -44,8 +47,8 @@ public class NoTermComputerScreen<T extends AbstractComputerMenu> extends Screen
|
||||
protected void init() {
|
||||
// First ensure we're still grabbing the mouse, so the user can look around. Then reset bits of state that
|
||||
// grabbing unsets.
|
||||
minecraft.mouseHandler.grabMouse();
|
||||
minecraft.screen = this;
|
||||
minecraft().mouseHandler.grabMouse();
|
||||
minecraft().screen = this;
|
||||
KeyMapping.releaseAll();
|
||||
|
||||
super.init();
|
||||
@ -64,13 +67,13 @@ public class NoTermComputerScreen<T extends AbstractComputerMenu> extends Screen
|
||||
|
||||
@Override
|
||||
public boolean mouseScrolled(double mouseX, double mouseY, double scrollX, double scrollY) {
|
||||
minecraft.player.getInventory().swapPaint(scrollX);
|
||||
Objects.requireNonNull(minecraft().player).getInventory().swapPaint(scrollY);
|
||||
return super.mouseScrolled(mouseX, mouseY, scrollX, scrollY);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onClose() {
|
||||
minecraft.player.closeContainer();
|
||||
Objects.requireNonNull(minecraft().player).closeContainer();
|
||||
super.onClose();
|
||||
}
|
||||
|
||||
@ -93,12 +96,16 @@ public class NoTermComputerScreen<T extends AbstractComputerMenu> extends Screen
|
||||
public void render(GuiGraphics graphics, int mouseX, int mouseY, float partialTicks) {
|
||||
super.render(graphics, mouseX, mouseY, partialTicks);
|
||||
|
||||
var font = minecraft.font;
|
||||
var font = minecraft().font;
|
||||
var lines = font.split(Component.translatable("gui.computercraft.pocket_computer_overlay"), (int) (width * 0.8));
|
||||
var y = 10;
|
||||
for (var line : lines) {
|
||||
graphics.drawString(font, line, (width / 2) - (minecraft.font.width(line) / 2), y, 0xFFFFFF, true);
|
||||
graphics.drawString(font, line, (width / 2) - (font.width(line) / 2), y, 0xFFFFFF, true);
|
||||
y += 9;
|
||||
}
|
||||
}
|
||||
|
||||
private Minecraft minecraft() {
|
||||
return Nullability.assertNonNull(minecraft);
|
||||
}
|
||||
}
|
||||
|
@ -66,13 +66,13 @@ public class PrintoutScreen extends AbstractContainerScreen<HeldItemMenu> {
|
||||
@Override
|
||||
public boolean mouseScrolled(double x, double y, double deltaX, double deltaY) {
|
||||
if (super.mouseScrolled(x, y, deltaX, deltaY)) return true;
|
||||
if (deltaX < 0) {
|
||||
if (deltaY < 0) {
|
||||
// Scroll up goes to the next page
|
||||
if (page < pages - 1) page++;
|
||||
return true;
|
||||
}
|
||||
|
||||
if (deltaX > 0) {
|
||||
if (deltaY > 0) {
|
||||
// Scroll down goes to the previous page
|
||||
if (page > 0) page--;
|
||||
return true;
|
||||
|
@ -195,16 +195,16 @@ public class TerminalWidget extends AbstractWidget {
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean mouseScrolled(double mouseX, double mouseY, double delta, double deltaY) {
|
||||
public boolean mouseScrolled(double mouseX, double mouseY, double deltaX, double deltaY) {
|
||||
if (!inTermRegion(mouseX, mouseY)) return false;
|
||||
if (!hasMouseSupport() || delta == 0) return false;
|
||||
if (!hasMouseSupport() || deltaY == 0) return false;
|
||||
|
||||
var charX = (int) ((mouseX - innerX) / FONT_WIDTH);
|
||||
var charY = (int) ((mouseY - innerY) / FONT_HEIGHT);
|
||||
charX = Math.min(Math.max(charX, 0), terminal.getWidth() - 1);
|
||||
charY = Math.min(Math.max(charY, 0), terminal.getHeight() - 1);
|
||||
|
||||
computer.mouseScroll(delta < 0 ? 1 : -1, charX + 1, charY + 1);
|
||||
computer.mouseScroll(deltaY < 0 ? 1 : -1, charX + 1, charY + 1);
|
||||
|
||||
lastMouseX = charX;
|
||||
lastMouseY = charY;
|
||||
|
@ -6,7 +6,6 @@ package dan200.computercraft.client.pocket;
|
||||
|
||||
import dan200.computercraft.shared.computer.core.ComputerState;
|
||||
import dan200.computercraft.shared.computer.core.ServerComputer;
|
||||
import dan200.computercraft.shared.computer.terminal.NetworkedTerminal;
|
||||
import dan200.computercraft.shared.computer.terminal.TerminalState;
|
||||
import dan200.computercraft.shared.network.client.PocketComputerDataMessage;
|
||||
import dan200.computercraft.shared.pocket.items.PocketComputerItem;
|
||||
@ -47,13 +46,10 @@ public final class ClientPocketComputers {
|
||||
public static void setState(UUID instanceId, ComputerState state, int lightColour, TerminalState terminalData) {
|
||||
var computer = instances.get(instanceId);
|
||||
if (computer == null) {
|
||||
var terminal = new NetworkedTerminal(terminalData.width, terminalData.height, terminalData.colour);
|
||||
instances.put(instanceId, computer = new PocketComputerData(state, lightColour, terminal));
|
||||
instances.put(instanceId, new PocketComputerData(state, lightColour, terminalData));
|
||||
} else {
|
||||
computer.setState(state, lightColour);
|
||||
computer.setState(state, lightColour, terminalData);
|
||||
}
|
||||
|
||||
if (terminalData.hasTerminal()) terminalData.apply(computer.getTerminal());
|
||||
}
|
||||
|
||||
public static @Nullable PocketComputerData get(ItemStack stack) {
|
||||
|
@ -6,8 +6,11 @@ package dan200.computercraft.client.pocket;
|
||||
|
||||
import dan200.computercraft.shared.computer.core.ComputerState;
|
||||
import dan200.computercraft.shared.computer.terminal.NetworkedTerminal;
|
||||
import dan200.computercraft.shared.computer.terminal.TerminalState;
|
||||
import dan200.computercraft.shared.pocket.core.PocketServerComputer;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
/**
|
||||
* Clientside data about a pocket computer.
|
||||
* <p>
|
||||
@ -19,21 +22,21 @@ import dan200.computercraft.shared.pocket.core.PocketServerComputer;
|
||||
* @see PocketServerComputer The server-side pocket computer.
|
||||
*/
|
||||
public final class PocketComputerData {
|
||||
private final NetworkedTerminal terminal;
|
||||
private @Nullable NetworkedTerminal terminal;
|
||||
private ComputerState state;
|
||||
private int lightColour;
|
||||
|
||||
PocketComputerData(ComputerState state, int lightColour, NetworkedTerminal terminal) {
|
||||
PocketComputerData(ComputerState state, int lightColour, TerminalState terminalData) {
|
||||
this.state = state;
|
||||
this.lightColour = lightColour;
|
||||
this.terminal = terminal;
|
||||
if (terminalData.hasTerminal()) terminal = terminalData.create();
|
||||
}
|
||||
|
||||
public int getLightState() {
|
||||
return state != ComputerState.OFF ? lightColour : -1;
|
||||
}
|
||||
|
||||
public NetworkedTerminal getTerminal() {
|
||||
public @Nullable NetworkedTerminal getTerminal() {
|
||||
return terminal;
|
||||
}
|
||||
|
||||
@ -41,8 +44,16 @@ public final class PocketComputerData {
|
||||
return state;
|
||||
}
|
||||
|
||||
void setState(ComputerState state, int lightColour) {
|
||||
void setState(ComputerState state, int lightColour, TerminalState terminalData) {
|
||||
this.state = state;
|
||||
this.lightColour = lightColour;
|
||||
|
||||
if (terminalData.hasTerminal()) {
|
||||
if (terminal == null) {
|
||||
terminal = terminalData.create();
|
||||
} else {
|
||||
terminalData.apply(terminal);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -17,6 +17,8 @@ import net.minecraft.world.entity.player.Player;
|
||||
import net.minecraft.world.item.ItemDisplayContext;
|
||||
import net.minecraft.world.item.ItemStack;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
* A base class for items which have map-like rendering when held in the hand.
|
||||
*
|
||||
@ -35,7 +37,7 @@ public abstract class ItemMapLikeRenderer {
|
||||
protected abstract void renderItem(PoseStack transform, MultiBufferSource render, ItemStack stack, int light);
|
||||
|
||||
public void renderItemFirstPerson(PoseStack transform, MultiBufferSource render, int lightTexture, InteractionHand hand, float pitch, float equipProgress, float swingProgress, ItemStack stack) {
|
||||
Player player = Minecraft.getInstance().player;
|
||||
Player player = Objects.requireNonNull(Minecraft.getInstance().player);
|
||||
|
||||
transform.pushPose();
|
||||
if (hand == InteractionHand.MAIN_HAND && player.getOffhandItem().isEmpty()) {
|
||||
|
@ -55,7 +55,7 @@ public class TurtleBlockEntityRenderer implements BlockEntityRenderer<TurtleBloc
|
||||
// Render the label
|
||||
var label = turtle.getLabel();
|
||||
var hit = renderer.cameraHitResult;
|
||||
if (label != null && hit.getType() == HitResult.Type.BLOCK && turtle.getBlockPos().equals(((BlockHitResult) hit).getBlockPos())) {
|
||||
if (label != null && hit != null && hit.getType() == HitResult.Type.BLOCK && turtle.getBlockPos().equals(((BlockHitResult) hit).getBlockPos())) {
|
||||
var mc = Minecraft.getInstance();
|
||||
var font = this.font;
|
||||
|
||||
|
@ -8,6 +8,7 @@ import dan200.computercraft.api.ComputerCraftTags;
|
||||
import dan200.computercraft.impl.RegistryHelper;
|
||||
import dan200.computercraft.shared.ModRegistry;
|
||||
import net.minecraft.core.Registry;
|
||||
import dan200.computercraft.shared.integration.ExternalModTags;
|
||||
import net.minecraft.data.tags.ItemTagsProvider;
|
||||
import net.minecraft.data.tags.TagsProvider;
|
||||
import net.minecraft.tags.BlockTags;
|
||||
@ -82,6 +83,12 @@ class TagProvider {
|
||||
);
|
||||
|
||||
tags.tag(BlockTags.WITHER_IMMUNE).add(ModRegistry.Blocks.COMPUTER_COMMAND.get());
|
||||
|
||||
tags.tag(ExternalModTags.Blocks.CREATE_BRITTLE).add(
|
||||
ModRegistry.Blocks.CABLE.get(),
|
||||
ModRegistry.Blocks.WIRELESS_MODEM_NORMAL.get(),
|
||||
ModRegistry.Blocks.WIRELESS_MODEM_ADVANCED.get()
|
||||
);
|
||||
}
|
||||
|
||||
public static void itemTags(ItemTagConsumer tags) {
|
||||
|
@ -223,7 +223,7 @@ public abstract class AbstractComputerBlockEntity extends BlockEntity implements
|
||||
var offsetSide = dir.getOpposite();
|
||||
var localDir = remapToLocalSide(dir);
|
||||
|
||||
computer.setRedstoneInput(localDir, RedstoneUtil.getRedstoneInput(level, targetPos, dir));
|
||||
computer.setRedstoneInput(localDir, RedstoneUtil.getRedstoneInput(getLevel(), targetPos, dir));
|
||||
computer.setBundledRedstoneInput(localDir, BundledRedstone.getOutput(getLevel(), targetPos, offsetSide));
|
||||
}
|
||||
|
||||
|
@ -28,13 +28,14 @@ public class ServerComputerRegistry {
|
||||
}
|
||||
|
||||
void update() {
|
||||
var it = getComputers().iterator();
|
||||
var it = computersByInstanceUuid.values().iterator();
|
||||
while (it.hasNext()) {
|
||||
var computer = it.next();
|
||||
if (computer.hasTimedOut()) {
|
||||
computer.unload();
|
||||
computer.onRemoved();
|
||||
it.remove();
|
||||
computersByInstanceUuid.remove(computer.getInstanceUUID());
|
||||
} else {
|
||||
computer.tickServer();
|
||||
}
|
||||
|
@ -18,10 +18,9 @@ import javax.annotation.Nullable;
|
||||
* states, etc...
|
||||
*/
|
||||
public class TerminalState {
|
||||
public final boolean colour;
|
||||
|
||||
public final int width;
|
||||
public final int height;
|
||||
private final boolean colour;
|
||||
private final int width;
|
||||
private final int height;
|
||||
|
||||
@Nullable
|
||||
private final ByteBuf buffer;
|
||||
|
@ -0,0 +1,37 @@
|
||||
// SPDX-FileCopyrightText: 2024 The CC: Tweaked Developers
|
||||
//
|
||||
// SPDX-License-Identifier: MPL-2.0
|
||||
|
||||
package dan200.computercraft.shared.integration;
|
||||
|
||||
import net.minecraft.core.registries.Registries;
|
||||
import net.minecraft.resources.ResourceLocation;
|
||||
import net.minecraft.tags.TagKey;
|
||||
import net.minecraft.world.level.block.Block;
|
||||
|
||||
/**
|
||||
* Tags defined by external mods.
|
||||
*/
|
||||
public final class ExternalModTags {
|
||||
private ExternalModTags() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Block tags defined by external mods.
|
||||
*/
|
||||
public static final class Blocks {
|
||||
private Blocks() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Create's "brittle" tag, used to determine if this block needs to be moved before its neighbours.
|
||||
*
|
||||
* @see <a href="https://github.com/Creators-of-Create/Create/blob/mc1.20.1/dev/src/main/java/com/simibubi/create/content/contraptions/BlockMovementChecks.java">{@code BlockMovementChecks}</a>
|
||||
*/
|
||||
public static final TagKey<Block> CREATE_BRITTLE = make("create", "brittle");
|
||||
|
||||
private static TagKey<Block> make(String mod, String name) {
|
||||
return TagKey.create(Registries.BLOCK, new ResourceLocation(mod, name));
|
||||
}
|
||||
}
|
||||
}
|
@ -303,7 +303,7 @@ public final class DiskDriveBlockEntity extends AbstractContainerBlockEntity {
|
||||
|
||||
// Set the id (if needed) and write it back to the media stack.
|
||||
var stack = media.stack().copy();
|
||||
mount = media.media().createDataMount(stack, (ServerLevel) level);
|
||||
mount = media.media().createDataMount(stack, (ServerLevel) getLevel());
|
||||
updateMediaStack(stack, immediate);
|
||||
|
||||
return mount;
|
||||
|
@ -4,7 +4,10 @@
|
||||
|
||||
package dan200.computercraft.shared.peripheral.modem;
|
||||
|
||||
import net.minecraft.core.BlockPos;
|
||||
import net.minecraft.core.Direction;
|
||||
import net.minecraft.world.level.LevelReader;
|
||||
import net.minecraft.world.level.block.Block;
|
||||
import net.minecraft.world.phys.shapes.Shapes;
|
||||
import net.minecraft.world.phys.shapes.VoxelShape;
|
||||
|
||||
@ -22,4 +25,17 @@ public final class ModemShapes {
|
||||
var direction = facing.ordinal();
|
||||
return direction < BOXES.length ? BOXES[direction] : Shapes.block();
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if a block can support a modem.
|
||||
*
|
||||
* @param level The current level.
|
||||
* @param pos The position of the adjacent block.
|
||||
* @param side The side the modem will be placed against.
|
||||
* @return Whether this block can support a modem.
|
||||
*/
|
||||
public static boolean canSupport(LevelReader level, BlockPos pos, Direction side) {
|
||||
// TODO(1.20.4): Check the side is a full-block instead.
|
||||
return Block.canSupportCenter(level, pos, side);
|
||||
}
|
||||
}
|
||||
|
@ -6,6 +6,7 @@ package dan200.computercraft.shared.peripheral.modem.wired;
|
||||
|
||||
import dan200.computercraft.annotations.ForgeOverride;
|
||||
import dan200.computercraft.shared.ModRegistry;
|
||||
import dan200.computercraft.shared.peripheral.modem.ModemShapes;
|
||||
import dan200.computercraft.shared.platform.PlatformHelper;
|
||||
import dan200.computercraft.shared.util.WaterloggableHelpers;
|
||||
import dan200.computercraft.shared.util.WorldUtil;
|
||||
@ -183,7 +184,7 @@ public class CableBlock extends Block implements SimpleWaterloggedBlock, EntityB
|
||||
|
||||
// Pop our modem if needed.
|
||||
var dir = state.getValue(MODEM).getFacing();
|
||||
if (dir != null && dir.equals(side) && !canSupportCenter(level, otherPos, side.getOpposite())) {
|
||||
if (dir != null && dir.equals(side) && !ModemShapes.canSupport(level, otherPos, side.getOpposite())) {
|
||||
// If we've no cable, follow normal Minecraft logic and just remove the block.
|
||||
if (!state.getValue(CABLE)) return getFluidState(state).createLegacyBlock();
|
||||
|
||||
@ -212,7 +213,7 @@ public class CableBlock extends Block implements SimpleWaterloggedBlock, EntityB
|
||||
var facing = state.getValue(MODEM).getFacing();
|
||||
if (facing == null) return true;
|
||||
|
||||
return canSupportCenter(world, pos.relative(facing), facing.getOpposite());
|
||||
return ModemShapes.canSupport(world, pos.relative(facing), facing.getOpposite());
|
||||
}
|
||||
|
||||
@Nullable
|
||||
|
@ -107,7 +107,7 @@ public class CableBlockEntity extends BlockEntity {
|
||||
|
||||
void neighborChanged(BlockPos neighbour) {
|
||||
var dir = getModemDirection();
|
||||
if (!level.isClientSide && dir != null && getBlockPos().relative(dir).equals(neighbour) && isPeripheralOn()) {
|
||||
if (!getLevel().isClientSide && dir != null && getBlockPos().relative(dir).equals(neighbour) && isPeripheralOn()) {
|
||||
queueRefreshPeripheral();
|
||||
}
|
||||
}
|
||||
@ -163,7 +163,7 @@ public class CableBlockEntity extends BlockEntity {
|
||||
.from(oldVariant.getFacing(), modem.getModemState().isOpen(), peripheral.hasPeripheral());
|
||||
|
||||
if (oldVariant != newVariant) {
|
||||
level.setBlockAndUpdate(getBlockPos(), state.setValue(CableBlock.MODEM, newVariant));
|
||||
getLevel().setBlockAndUpdate(getBlockPos(), state.setValue(CableBlock.MODEM, newVariant));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -84,7 +84,7 @@ public class WirelessModemBlock extends DirectionalBlock implements SimpleWaterl
|
||||
@Deprecated
|
||||
public boolean canSurvive(BlockState state, LevelReader world, BlockPos pos) {
|
||||
var facing = state.getValue(FACING);
|
||||
return canSupportCenter(world, pos.relative(facing), facing.getOpposite());
|
||||
return ModemShapes.canSupport(world, pos.relative(facing), facing.getOpposite());
|
||||
}
|
||||
|
||||
@Nullable
|
||||
|
@ -56,8 +56,11 @@ public final class ClientMonitor {
|
||||
|
||||
void read(TerminalState state) {
|
||||
if (state.hasTerminal()) {
|
||||
if (terminal == null) terminal = new NetworkedTerminal(state.width, state.height, state.colour);
|
||||
state.apply(terminal);
|
||||
if (terminal == null) {
|
||||
terminal = state.create();
|
||||
} else {
|
||||
state.apply(terminal);
|
||||
}
|
||||
terminalChanged = true;
|
||||
} else {
|
||||
if (terminal != null) {
|
||||
|
@ -174,7 +174,7 @@ public class MonitorBlockEntity extends BlockEntity {
|
||||
} else {
|
||||
// Otherwise fetch the origin and attempt to get its monitor
|
||||
// Note this may load chunks, but we don't really have a choice here.
|
||||
var te = level.getBlockEntity(toWorldPos(0, 0));
|
||||
var te = getLevel().getBlockEntity(toWorldPos(0, 0));
|
||||
if (!(te instanceof MonitorBlockEntity monitor)) return null;
|
||||
|
||||
return serverMonitor = monitor.createServerMonitor();
|
||||
@ -416,7 +416,7 @@ public class MonitorBlockEntity extends BlockEntity {
|
||||
|
||||
@Nullable
|
||||
private MonitorBlockEntity tryResizeAt(BlockPos pos, int width, int height) {
|
||||
var tile = level.getBlockEntity(pos);
|
||||
var tile = getLevel().getBlockEntity(pos);
|
||||
if (tile instanceof MonitorBlockEntity monitor && isCompatible(monitor)) {
|
||||
monitor.resize(width, height);
|
||||
return monitor;
|
||||
|
@ -19,15 +19,16 @@ import dan200.computercraft.shared.network.client.SpeakerPlayClientMessage;
|
||||
import dan200.computercraft.shared.network.client.SpeakerStopClientMessage;
|
||||
import dan200.computercraft.shared.network.server.ServerNetworking;
|
||||
import dan200.computercraft.shared.util.PauseAwareTimer;
|
||||
import net.minecraft.ResourceLocationException;
|
||||
import net.minecraft.core.BlockPos;
|
||||
import net.minecraft.core.Holder;
|
||||
import net.minecraft.core.registries.BuiltInRegistries;
|
||||
import net.minecraft.network.protocol.game.ClientboundSoundPacket;
|
||||
import net.minecraft.resources.ResourceLocation;
|
||||
import net.minecraft.server.level.ServerLevel;
|
||||
import net.minecraft.sounds.SoundEvent;
|
||||
import net.minecraft.sounds.SoundSource;
|
||||
import net.minecraft.util.Mth;
|
||||
import net.minecraft.world.item.RecordItem;
|
||||
import net.minecraft.world.level.block.state.properties.NoteBlockInstrument;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
@ -252,15 +253,15 @@ public abstract class SpeakerPeripheral implements IPeripheral {
|
||||
var volume = (float) clampVolume(checkFinite(1, volumeA.orElse(1.0)));
|
||||
var pitch = (float) checkFinite(2, pitchA.orElse(1.0));
|
||||
|
||||
ResourceLocation identifier;
|
||||
try {
|
||||
identifier = new ResourceLocation(name);
|
||||
} catch (ResourceLocationException e) {
|
||||
throw new LuaException("Malformed sound name '" + name + "' ");
|
||||
}
|
||||
var identifier = ResourceLocation.tryParse(name);
|
||||
if (identifier == null) throw new LuaException("Malformed sound name '" + name + "' ");
|
||||
|
||||
// Prevent playing music discs.
|
||||
var soundEvent = BuiltInRegistries.SOUND_EVENT.get(identifier);
|
||||
if (soundEvent != null && RecordItem.getBySound(soundEvent) != null) return false;
|
||||
|
||||
synchronized (lock) {
|
||||
if (dfpwmState != null && dfpwmState.isPlaying()) return false;
|
||||
if (pendingSound != null || (dfpwmState != null && dfpwmState.isPlaying())) return false;
|
||||
dfpwmState = null;
|
||||
pendingSound = new PendingSound<>(identifier, volume, pitch);
|
||||
return true;
|
||||
|
@ -165,7 +165,7 @@ public class TurtleBlockEntity extends AbstractComputerBlockEntity implements Ba
|
||||
|
||||
public void setDirection(Direction dir) {
|
||||
if (dir.getAxis() == Direction.Axis.Y) dir = Direction.NORTH;
|
||||
level.setBlockAndUpdate(worldPosition, getBlockState().setValue(TurtleBlock.FACING, dir));
|
||||
getLevel().setBlockAndUpdate(worldPosition, getBlockState().setValue(TurtleBlock.FACING, dir));
|
||||
|
||||
updateRedstone();
|
||||
updateInputsImmediately();
|
||||
|
@ -11,7 +11,8 @@ import org.junit.jupiter.api.RepeatedTest;
|
||||
|
||||
import java.util.Random;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertNotNull;
|
||||
|
||||
/**
|
||||
* Tests {@link TerminalState} round tripping works as expected.
|
||||
@ -42,6 +43,7 @@ public class TerminalStateTest {
|
||||
private static void checkEqual(Terminal expected, Terminal actual) {
|
||||
assertNotNull(expected, "Expected cannot be null");
|
||||
assertNotNull(actual, "Actual cannot be null");
|
||||
assertEquals(expected.isColour(), actual.isColour(), "isColour must match");
|
||||
assertEquals(expected.getHeight(), actual.getHeight(), "Heights must match");
|
||||
assertEquals(expected.getWidth(), actual.getWidth(), "Widths must match");
|
||||
|
||||
@ -51,13 +53,6 @@ public class TerminalStateTest {
|
||||
}
|
||||
|
||||
private static NetworkedTerminal read(FriendlyByteBuf buffer) {
|
||||
var state = new TerminalState(buffer);
|
||||
assertTrue(state.colour);
|
||||
|
||||
if (!state.hasTerminal()) return null;
|
||||
|
||||
var other = new NetworkedTerminal(state.width, state.height, true);
|
||||
state.apply(other);
|
||||
return other;
|
||||
return new TerminalState(buffer).create();
|
||||
}
|
||||
}
|
||||
|
@ -34,6 +34,7 @@ import java.io.Writer;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.HashSet;
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
@ -82,7 +83,7 @@ public class Exporter {
|
||||
}
|
||||
|
||||
// Now find all CC recipes.
|
||||
var level = Minecraft.getInstance().level;
|
||||
var level = Objects.requireNonNull(Minecraft.getInstance().level);
|
||||
for (var recipe : level.getRecipeManager().getAllRecipesFor(RecipeType.CRAFTING)) {
|
||||
var result = recipe.value().getResultItem(level.registryAccess());
|
||||
if (!RegistryHelper.getKeyOrThrow(BuiltInRegistries.ITEM, result.getItem()).getNamespace().equals(ComputerCraftAPI.MOD_ID)) {
|
||||
|
@ -41,7 +41,7 @@ class Pocket_Computer_Test {
|
||||
val pocketComputer = ClientPocketComputers.get(minecraft.player!!.mainHandItem)!!
|
||||
assertEquals(ComputerState.ON, pocketComputer.state)
|
||||
|
||||
val term = pocketComputer.terminal
|
||||
val term = pocketComputer.terminal!!
|
||||
assertEquals("Hello, world!", term.getLine(0).toString().trim(), "Terminal contents is synced")
|
||||
}
|
||||
// Update the terminal contents again.
|
||||
@ -57,7 +57,7 @@ class Pocket_Computer_Test {
|
||||
val pocketComputer = ClientPocketComputers.get(minecraft.player!!.mainHandItem)!!
|
||||
assertEquals(ComputerState.BLINKING, pocketComputer.state)
|
||||
|
||||
val term = pocketComputer.terminal
|
||||
val term = pocketComputer.terminal!!
|
||||
assertEquals("Updated text :)", term.getLine(0).toString().trim(), "Terminal contents is synced")
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,43 @@
|
||||
// SPDX-FileCopyrightText: 2024 The CC: Tweaked Developers
|
||||
//
|
||||
// SPDX-License-Identifier: MPL-2.0
|
||||
|
||||
package dan200.computercraft.gametest
|
||||
|
||||
import dan200.computercraft.gametest.api.sequence
|
||||
import dan200.computercraft.gametest.api.thenOnComputer
|
||||
import dan200.computercraft.gametest.api.tryMultipleTimes
|
||||
import dan200.computercraft.shared.peripheral.speaker.SpeakerPeripheral
|
||||
import dan200.computercraft.test.core.assertArrayEquals
|
||||
import net.minecraft.gametest.framework.GameTest
|
||||
import net.minecraft.gametest.framework.GameTestHelper
|
||||
import net.minecraft.sounds.SoundEvents
|
||||
|
||||
class Speaker_Test {
|
||||
/**
|
||||
* [SpeakerPeripheral.playSound] fails if there is already a sound queued.
|
||||
*/
|
||||
@GameTest
|
||||
fun Fails_to_play_multiple_sounds(helper: GameTestHelper) = helper.sequence {
|
||||
thenOnComputer {
|
||||
callPeripheral("right", "playSound", SoundEvents.NOTE_BLOCK_HARP.key().location().toString())
|
||||
.assertArrayEquals(true)
|
||||
|
||||
tryMultipleTimes(2) { // We could technically call this a tick later, so try twice
|
||||
callPeripheral("right", "playSound", SoundEvents.NOTE_BLOCK_HARP.key().location().toString())
|
||||
.assertArrayEquals(false)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* [SpeakerPeripheral.playSound] will not play records.
|
||||
*/
|
||||
@GameTest
|
||||
fun Will_not_play_record(helper: GameTestHelper) = helper.sequence {
|
||||
thenOnComputer {
|
||||
callPeripheral("right", "playSound", SoundEvents.MUSIC_DISC_PIGSTEP.location.toString())
|
||||
.assertArrayEquals(false)
|
||||
}
|
||||
}
|
||||
}
|
@ -323,3 +323,16 @@ fun GameTestHelper.placeItemAt(stack: ItemStack, pos: BlockPos, direction: Direc
|
||||
val hit = BlockHitResult(Vec3.atCenterOf(absolutePos), direction, absolutePos, false)
|
||||
stack.useOn(UseOnContext(player, InteractionHand.MAIN_HAND, hit))
|
||||
}
|
||||
|
||||
/**
|
||||
* Run a function multiple times until it succeeds.
|
||||
*/
|
||||
inline fun tryMultipleTimes(count: Int, action: () -> Unit) {
|
||||
for (remaining in count - 1 downTo 0) {
|
||||
try {
|
||||
action()
|
||||
} catch (e: AssertionError) {
|
||||
if (remaining == 0) throw e
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -87,6 +87,7 @@ object TestHooks {
|
||||
Printer_Test::class.java,
|
||||
Printout_Test::class.java,
|
||||
Recipe_Test::class.java,
|
||||
Speaker_Test::class.java,
|
||||
Turtle_Test::class.java,
|
||||
)
|
||||
|
||||
|
@ -0,0 +1,138 @@
|
||||
{
|
||||
DataVersion: 3465,
|
||||
size: [5, 5, 5],
|
||||
data: [
|
||||
{pos: [0, 0, 0], state: "minecraft:polished_andesite"},
|
||||
{pos: [0, 0, 1], state: "minecraft:polished_andesite"},
|
||||
{pos: [0, 0, 2], state: "minecraft:polished_andesite"},
|
||||
{pos: [0, 0, 3], state: "minecraft:polished_andesite"},
|
||||
{pos: [0, 0, 4], state: "minecraft:polished_andesite"},
|
||||
{pos: [1, 0, 0], state: "minecraft:polished_andesite"},
|
||||
{pos: [1, 0, 1], state: "minecraft:polished_andesite"},
|
||||
{pos: [1, 0, 2], state: "minecraft:polished_andesite"},
|
||||
{pos: [1, 0, 3], state: "minecraft:polished_andesite"},
|
||||
{pos: [1, 0, 4], state: "minecraft:polished_andesite"},
|
||||
{pos: [2, 0, 0], state: "minecraft:polished_andesite"},
|
||||
{pos: [2, 0, 1], state: "minecraft:polished_andesite"},
|
||||
{pos: [2, 0, 2], state: "minecraft:polished_andesite"},
|
||||
{pos: [2, 0, 3], state: "minecraft:polished_andesite"},
|
||||
{pos: [2, 0, 4], state: "minecraft:polished_andesite"},
|
||||
{pos: [3, 0, 0], state: "minecraft:polished_andesite"},
|
||||
{pos: [3, 0, 1], state: "minecraft:polished_andesite"},
|
||||
{pos: [3, 0, 2], state: "minecraft:polished_andesite"},
|
||||
{pos: [3, 0, 3], state: "minecraft:polished_andesite"},
|
||||
{pos: [3, 0, 4], state: "minecraft:polished_andesite"},
|
||||
{pos: [4, 0, 0], state: "minecraft:polished_andesite"},
|
||||
{pos: [4, 0, 1], state: "minecraft:polished_andesite"},
|
||||
{pos: [4, 0, 2], state: "minecraft:polished_andesite"},
|
||||
{pos: [4, 0, 3], state: "minecraft:polished_andesite"},
|
||||
{pos: [4, 0, 4], state: "minecraft:polished_andesite"},
|
||||
{pos: [0, 1, 0], state: "minecraft:air"},
|
||||
{pos: [0, 1, 1], state: "minecraft:air"},
|
||||
{pos: [0, 1, 2], state: "minecraft:air"},
|
||||
{pos: [0, 1, 3], state: "minecraft:air"},
|
||||
{pos: [0, 1, 4], state: "minecraft:air"},
|
||||
{pos: [1, 1, 0], state: "minecraft:air"},
|
||||
{pos: [1, 1, 1], state: "minecraft:air"},
|
||||
{pos: [1, 1, 2], state: "computercraft:speaker{facing:north}", nbt: {id: "computercraft:speaker"}},
|
||||
{pos: [1, 1, 3], state: "minecraft:air"},
|
||||
{pos: [1, 1, 4], state: "minecraft:air"},
|
||||
{pos: [2, 1, 0], state: "minecraft:air"},
|
||||
{pos: [2, 1, 1], state: "minecraft:air"},
|
||||
{pos: [2, 1, 2], state: "computercraft:computer_normal{facing:north,state:on}", nbt: {ComputerId: 1, Label: "speaker_test.fails_to_play_multiple_sounds", On: 1b, id: "computercraft:computer_normal"}},
|
||||
{pos: [2, 1, 3], state: "minecraft:air"},
|
||||
{pos: [2, 1, 4], state: "minecraft:air"},
|
||||
{pos: [3, 1, 0], state: "minecraft:air"},
|
||||
{pos: [3, 1, 1], state: "minecraft:air"},
|
||||
{pos: [3, 1, 2], state: "minecraft:air"},
|
||||
{pos: [3, 1, 3], state: "minecraft:air"},
|
||||
{pos: [3, 1, 4], state: "minecraft:air"},
|
||||
{pos: [4, 1, 0], state: "minecraft:air"},
|
||||
{pos: [4, 1, 1], state: "minecraft:air"},
|
||||
{pos: [4, 1, 2], state: "minecraft:air"},
|
||||
{pos: [4, 1, 3], state: "minecraft:air"},
|
||||
{pos: [4, 1, 4], state: "minecraft:air"},
|
||||
{pos: [0, 2, 0], state: "minecraft:air"},
|
||||
{pos: [0, 2, 1], state: "minecraft:air"},
|
||||
{pos: [0, 2, 2], state: "minecraft:air"},
|
||||
{pos: [0, 2, 3], state: "minecraft:air"},
|
||||
{pos: [0, 2, 4], state: "minecraft:air"},
|
||||
{pos: [1, 2, 0], state: "minecraft:air"},
|
||||
{pos: [1, 2, 1], state: "minecraft:air"},
|
||||
{pos: [1, 2, 2], state: "minecraft:air"},
|
||||
{pos: [1, 2, 3], state: "minecraft:air"},
|
||||
{pos: [1, 2, 4], state: "minecraft:air"},
|
||||
{pos: [2, 2, 0], state: "minecraft:air"},
|
||||
{pos: [2, 2, 1], state: "minecraft:air"},
|
||||
{pos: [2, 2, 2], state: "minecraft:air"},
|
||||
{pos: [2, 2, 3], state: "minecraft:air"},
|
||||
{pos: [2, 2, 4], state: "minecraft:air"},
|
||||
{pos: [3, 2, 0], state: "minecraft:air"},
|
||||
{pos: [3, 2, 1], state: "minecraft:air"},
|
||||
{pos: [3, 2, 2], state: "minecraft:air"},
|
||||
{pos: [3, 2, 3], state: "minecraft:air"},
|
||||
{pos: [3, 2, 4], state: "minecraft:air"},
|
||||
{pos: [4, 2, 0], state: "minecraft:air"},
|
||||
{pos: [4, 2, 1], state: "minecraft:air"},
|
||||
{pos: [4, 2, 2], state: "minecraft:air"},
|
||||
{pos: [4, 2, 3], state: "minecraft:air"},
|
||||
{pos: [4, 2, 4], state: "minecraft:air"},
|
||||
{pos: [0, 3, 0], state: "minecraft:air"},
|
||||
{pos: [0, 3, 1], state: "minecraft:air"},
|
||||
{pos: [0, 3, 2], state: "minecraft:air"},
|
||||
{pos: [0, 3, 3], state: "minecraft:air"},
|
||||
{pos: [0, 3, 4], state: "minecraft:air"},
|
||||
{pos: [1, 3, 0], state: "minecraft:air"},
|
||||
{pos: [1, 3, 1], state: "minecraft:air"},
|
||||
{pos: [1, 3, 2], state: "minecraft:air"},
|
||||
{pos: [1, 3, 3], state: "minecraft:air"},
|
||||
{pos: [1, 3, 4], state: "minecraft:air"},
|
||||
{pos: [2, 3, 0], state: "minecraft:air"},
|
||||
{pos: [2, 3, 1], state: "minecraft:air"},
|
||||
{pos: [2, 3, 2], state: "minecraft:air"},
|
||||
{pos: [2, 3, 3], state: "minecraft:air"},
|
||||
{pos: [2, 3, 4], state: "minecraft:air"},
|
||||
{pos: [3, 3, 0], state: "minecraft:air"},
|
||||
{pos: [3, 3, 1], state: "minecraft:air"},
|
||||
{pos: [3, 3, 2], state: "minecraft:air"},
|
||||
{pos: [3, 3, 3], state: "minecraft:air"},
|
||||
{pos: [3, 3, 4], state: "minecraft:air"},
|
||||
{pos: [4, 3, 0], state: "minecraft:air"},
|
||||
{pos: [4, 3, 1], state: "minecraft:air"},
|
||||
{pos: [4, 3, 2], state: "minecraft:air"},
|
||||
{pos: [4, 3, 3], state: "minecraft:air"},
|
||||
{pos: [4, 3, 4], state: "minecraft:air"},
|
||||
{pos: [0, 4, 0], state: "minecraft:air"},
|
||||
{pos: [0, 4, 1], state: "minecraft:air"},
|
||||
{pos: [0, 4, 2], state: "minecraft:air"},
|
||||
{pos: [0, 4, 3], state: "minecraft:air"},
|
||||
{pos: [0, 4, 4], state: "minecraft:air"},
|
||||
{pos: [1, 4, 0], state: "minecraft:air"},
|
||||
{pos: [1, 4, 1], state: "minecraft:air"},
|
||||
{pos: [1, 4, 2], state: "minecraft:air"},
|
||||
{pos: [1, 4, 3], state: "minecraft:air"},
|
||||
{pos: [1, 4, 4], state: "minecraft:air"},
|
||||
{pos: [2, 4, 0], state: "minecraft:air"},
|
||||
{pos: [2, 4, 1], state: "minecraft:air"},
|
||||
{pos: [2, 4, 2], state: "minecraft:air"},
|
||||
{pos: [2, 4, 3], state: "minecraft:air"},
|
||||
{pos: [2, 4, 4], state: "minecraft:air"},
|
||||
{pos: [3, 4, 0], state: "minecraft:air"},
|
||||
{pos: [3, 4, 1], state: "minecraft:air"},
|
||||
{pos: [3, 4, 2], state: "minecraft:air"},
|
||||
{pos: [3, 4, 3], state: "minecraft:air"},
|
||||
{pos: [3, 4, 4], state: "minecraft:air"},
|
||||
{pos: [4, 4, 0], state: "minecraft:air"},
|
||||
{pos: [4, 4, 1], state: "minecraft:air"},
|
||||
{pos: [4, 4, 2], state: "minecraft:air"},
|
||||
{pos: [4, 4, 3], state: "minecraft:air"},
|
||||
{pos: [4, 4, 4], state: "minecraft:air"}
|
||||
],
|
||||
entities: [],
|
||||
palette: [
|
||||
"minecraft:polished_andesite",
|
||||
"minecraft:air",
|
||||
"computercraft:speaker{facing:north}",
|
||||
"computercraft:computer_normal{facing:north,state:on}"
|
||||
]
|
||||
}
|
138
projects/common/src/testMod/resources/data/cctest/structures/speaker_test.will_not_play_record.snbt
generated
Normal file
138
projects/common/src/testMod/resources/data/cctest/structures/speaker_test.will_not_play_record.snbt
generated
Normal file
@ -0,0 +1,138 @@
|
||||
{
|
||||
DataVersion: 3465,
|
||||
size: [5, 5, 5],
|
||||
data: [
|
||||
{pos: [0, 0, 0], state: "minecraft:polished_andesite"},
|
||||
{pos: [0, 0, 1], state: "minecraft:polished_andesite"},
|
||||
{pos: [0, 0, 2], state: "minecraft:polished_andesite"},
|
||||
{pos: [0, 0, 3], state: "minecraft:polished_andesite"},
|
||||
{pos: [0, 0, 4], state: "minecraft:polished_andesite"},
|
||||
{pos: [1, 0, 0], state: "minecraft:polished_andesite"},
|
||||
{pos: [1, 0, 1], state: "minecraft:polished_andesite"},
|
||||
{pos: [1, 0, 2], state: "minecraft:polished_andesite"},
|
||||
{pos: [1, 0, 3], state: "minecraft:polished_andesite"},
|
||||
{pos: [1, 0, 4], state: "minecraft:polished_andesite"},
|
||||
{pos: [2, 0, 0], state: "minecraft:polished_andesite"},
|
||||
{pos: [2, 0, 1], state: "minecraft:polished_andesite"},
|
||||
{pos: [2, 0, 2], state: "minecraft:polished_andesite"},
|
||||
{pos: [2, 0, 3], state: "minecraft:polished_andesite"},
|
||||
{pos: [2, 0, 4], state: "minecraft:polished_andesite"},
|
||||
{pos: [3, 0, 0], state: "minecraft:polished_andesite"},
|
||||
{pos: [3, 0, 1], state: "minecraft:polished_andesite"},
|
||||
{pos: [3, 0, 2], state: "minecraft:polished_andesite"},
|
||||
{pos: [3, 0, 3], state: "minecraft:polished_andesite"},
|
||||
{pos: [3, 0, 4], state: "minecraft:polished_andesite"},
|
||||
{pos: [4, 0, 0], state: "minecraft:polished_andesite"},
|
||||
{pos: [4, 0, 1], state: "minecraft:polished_andesite"},
|
||||
{pos: [4, 0, 2], state: "minecraft:polished_andesite"},
|
||||
{pos: [4, 0, 3], state: "minecraft:polished_andesite"},
|
||||
{pos: [4, 0, 4], state: "minecraft:polished_andesite"},
|
||||
{pos: [0, 1, 0], state: "minecraft:air"},
|
||||
{pos: [0, 1, 1], state: "minecraft:air"},
|
||||
{pos: [0, 1, 2], state: "minecraft:air"},
|
||||
{pos: [0, 1, 3], state: "minecraft:air"},
|
||||
{pos: [0, 1, 4], state: "minecraft:air"},
|
||||
{pos: [1, 1, 0], state: "minecraft:air"},
|
||||
{pos: [1, 1, 1], state: "minecraft:air"},
|
||||
{pos: [1, 1, 2], state: "computercraft:speaker{facing:north}", nbt: {id: "computercraft:speaker"}},
|
||||
{pos: [1, 1, 3], state: "minecraft:air"},
|
||||
{pos: [1, 1, 4], state: "minecraft:air"},
|
||||
{pos: [2, 1, 0], state: "minecraft:air"},
|
||||
{pos: [2, 1, 1], state: "minecraft:air"},
|
||||
{pos: [2, 1, 2], state: "computercraft:computer_normal{facing:north,state:on}", nbt: {ComputerId: 1, Label: "speaker_test.will_not_play_record", On: 1b, id: "computercraft:computer_normal"}},
|
||||
{pos: [2, 1, 3], state: "minecraft:air"},
|
||||
{pos: [2, 1, 4], state: "minecraft:air"},
|
||||
{pos: [3, 1, 0], state: "minecraft:air"},
|
||||
{pos: [3, 1, 1], state: "minecraft:air"},
|
||||
{pos: [3, 1, 2], state: "minecraft:air"},
|
||||
{pos: [3, 1, 3], state: "minecraft:air"},
|
||||
{pos: [3, 1, 4], state: "minecraft:air"},
|
||||
{pos: [4, 1, 0], state: "minecraft:air"},
|
||||
{pos: [4, 1, 1], state: "minecraft:air"},
|
||||
{pos: [4, 1, 2], state: "minecraft:air"},
|
||||
{pos: [4, 1, 3], state: "minecraft:air"},
|
||||
{pos: [4, 1, 4], state: "minecraft:air"},
|
||||
{pos: [0, 2, 0], state: "minecraft:air"},
|
||||
{pos: [0, 2, 1], state: "minecraft:air"},
|
||||
{pos: [0, 2, 2], state: "minecraft:air"},
|
||||
{pos: [0, 2, 3], state: "minecraft:air"},
|
||||
{pos: [0, 2, 4], state: "minecraft:air"},
|
||||
{pos: [1, 2, 0], state: "minecraft:air"},
|
||||
{pos: [1, 2, 1], state: "minecraft:air"},
|
||||
{pos: [1, 2, 2], state: "minecraft:air"},
|
||||
{pos: [1, 2, 3], state: "minecraft:air"},
|
||||
{pos: [1, 2, 4], state: "minecraft:air"},
|
||||
{pos: [2, 2, 0], state: "minecraft:air"},
|
||||
{pos: [2, 2, 1], state: "minecraft:air"},
|
||||
{pos: [2, 2, 2], state: "minecraft:air"},
|
||||
{pos: [2, 2, 3], state: "minecraft:air"},
|
||||
{pos: [2, 2, 4], state: "minecraft:air"},
|
||||
{pos: [3, 2, 0], state: "minecraft:air"},
|
||||
{pos: [3, 2, 1], state: "minecraft:air"},
|
||||
{pos: [3, 2, 2], state: "minecraft:air"},
|
||||
{pos: [3, 2, 3], state: "minecraft:air"},
|
||||
{pos: [3, 2, 4], state: "minecraft:air"},
|
||||
{pos: [4, 2, 0], state: "minecraft:air"},
|
||||
{pos: [4, 2, 1], state: "minecraft:air"},
|
||||
{pos: [4, 2, 2], state: "minecraft:air"},
|
||||
{pos: [4, 2, 3], state: "minecraft:air"},
|
||||
{pos: [4, 2, 4], state: "minecraft:air"},
|
||||
{pos: [0, 3, 0], state: "minecraft:air"},
|
||||
{pos: [0, 3, 1], state: "minecraft:air"},
|
||||
{pos: [0, 3, 2], state: "minecraft:air"},
|
||||
{pos: [0, 3, 3], state: "minecraft:air"},
|
||||
{pos: [0, 3, 4], state: "minecraft:air"},
|
||||
{pos: [1, 3, 0], state: "minecraft:air"},
|
||||
{pos: [1, 3, 1], state: "minecraft:air"},
|
||||
{pos: [1, 3, 2], state: "minecraft:air"},
|
||||
{pos: [1, 3, 3], state: "minecraft:air"},
|
||||
{pos: [1, 3, 4], state: "minecraft:air"},
|
||||
{pos: [2, 3, 0], state: "minecraft:air"},
|
||||
{pos: [2, 3, 1], state: "minecraft:air"},
|
||||
{pos: [2, 3, 2], state: "minecraft:air"},
|
||||
{pos: [2, 3, 3], state: "minecraft:air"},
|
||||
{pos: [2, 3, 4], state: "minecraft:air"},
|
||||
{pos: [3, 3, 0], state: "minecraft:air"},
|
||||
{pos: [3, 3, 1], state: "minecraft:air"},
|
||||
{pos: [3, 3, 2], state: "minecraft:air"},
|
||||
{pos: [3, 3, 3], state: "minecraft:air"},
|
||||
{pos: [3, 3, 4], state: "minecraft:air"},
|
||||
{pos: [4, 3, 0], state: "minecraft:air"},
|
||||
{pos: [4, 3, 1], state: "minecraft:air"},
|
||||
{pos: [4, 3, 2], state: "minecraft:air"},
|
||||
{pos: [4, 3, 3], state: "minecraft:air"},
|
||||
{pos: [4, 3, 4], state: "minecraft:air"},
|
||||
{pos: [0, 4, 0], state: "minecraft:air"},
|
||||
{pos: [0, 4, 1], state: "minecraft:air"},
|
||||
{pos: [0, 4, 2], state: "minecraft:air"},
|
||||
{pos: [0, 4, 3], state: "minecraft:air"},
|
||||
{pos: [0, 4, 4], state: "minecraft:air"},
|
||||
{pos: [1, 4, 0], state: "minecraft:air"},
|
||||
{pos: [1, 4, 1], state: "minecraft:air"},
|
||||
{pos: [1, 4, 2], state: "minecraft:air"},
|
||||
{pos: [1, 4, 3], state: "minecraft:air"},
|
||||
{pos: [1, 4, 4], state: "minecraft:air"},
|
||||
{pos: [2, 4, 0], state: "minecraft:air"},
|
||||
{pos: [2, 4, 1], state: "minecraft:air"},
|
||||
{pos: [2, 4, 2], state: "minecraft:air"},
|
||||
{pos: [2, 4, 3], state: "minecraft:air"},
|
||||
{pos: [2, 4, 4], state: "minecraft:air"},
|
||||
{pos: [3, 4, 0], state: "minecraft:air"},
|
||||
{pos: [3, 4, 1], state: "minecraft:air"},
|
||||
{pos: [3, 4, 2], state: "minecraft:air"},
|
||||
{pos: [3, 4, 3], state: "minecraft:air"},
|
||||
{pos: [3, 4, 4], state: "minecraft:air"},
|
||||
{pos: [4, 4, 0], state: "minecraft:air"},
|
||||
{pos: [4, 4, 1], state: "minecraft:air"},
|
||||
{pos: [4, 4, 2], state: "minecraft:air"},
|
||||
{pos: [4, 4, 3], state: "minecraft:air"},
|
||||
{pos: [4, 4, 4], state: "minecraft:air"}
|
||||
],
|
||||
entities: [],
|
||||
palette: [
|
||||
"minecraft:polished_andesite",
|
||||
"minecraft:air",
|
||||
"computercraft:speaker{facing:north}",
|
||||
"computercraft:computer_normal{facing:north,state:on}"
|
||||
]
|
||||
}
|
@ -143,27 +143,27 @@ public class OSAPI implements ILuaAPI {
|
||||
|
||||
/**
|
||||
* Starts a timer that will run for the specified number of seconds. Once
|
||||
* the timer fires, a {@code timer} event will be added to the queue with
|
||||
* the ID returned from this function as the first parameter.
|
||||
* the timer fires, a [`timer`] event will be added to the queue with the ID
|
||||
* returned from this function as the first parameter.
|
||||
* <p>
|
||||
* As with [sleep][`os.sleep`], {@code timer} will automatically be rounded up
|
||||
* to the nearest multiple of 0.05 seconds, as it waits for a fixed amount
|
||||
* of world ticks.
|
||||
* As with [sleep][`os.sleep`], the time will automatically be rounded up to
|
||||
* the nearest multiple of 0.05 seconds, as it waits for a fixed amount of
|
||||
* world ticks.
|
||||
*
|
||||
* @param timer The number of seconds until the timer fires.
|
||||
* @return The ID of the new timer. This can be used to filter the
|
||||
* {@code timer} event, or {@link #cancelTimer cancel the timer}.
|
||||
* @param time The number of seconds until the timer fires.
|
||||
* @return The ID of the new timer. This can be used to filter the [`timer`]
|
||||
* event, or {@linkplain #cancelTimer cancel the timer}.
|
||||
* @throws LuaException If the time is below zero.
|
||||
* @see #cancelTimer To cancel a timer.
|
||||
*/
|
||||
@LuaFunction
|
||||
public final int startTimer(double timer) throws LuaException {
|
||||
return apiEnvironment.startTimer(Math.round(checkFinite(0, timer) / 0.05));
|
||||
public final int startTimer(double time) throws LuaException {
|
||||
return apiEnvironment.startTimer(Math.round(checkFinite(0, time) / 0.05));
|
||||
}
|
||||
|
||||
/**
|
||||
* Cancels a timer previously started with startTimer. This will stop the
|
||||
* timer from firing.
|
||||
* Cancels a timer previously started with {@link #startTimer(double)}. This
|
||||
* will stop the timer from firing.
|
||||
*
|
||||
* @param token The ID of the timer to cancel.
|
||||
* @cc.since 1.6
|
||||
@ -399,10 +399,9 @@ public class OSAPI implements ILuaAPI {
|
||||
* Returns a date string (or table) using a specified format string and
|
||||
* optional time to format.
|
||||
* <p>
|
||||
* The format string takes the same formats as C's {@code strftime} function
|
||||
* (http://www.cplusplus.com/reference/ctime/strftime/). In extension, it
|
||||
* can be prefixed with an exclamation mark ({@code !}) to use UTC time
|
||||
* instead of the server's local timezone.
|
||||
* The format string takes the same formats as C's [strftime](http://www.cplusplus.com/reference/ctime/strftime/)
|
||||
* function. The format string can also be prefixed with an exclamation mark
|
||||
* ({@code !}) to use UTC time instead of the server's local timezone.
|
||||
* <p>
|
||||
* If the format is exactly {@code *t} (optionally prefixed with {@code !}), a
|
||||
* table will be returned instead. This table has fields for the year, month,
|
||||
|
@ -13,6 +13,11 @@
|
||||
-- @module vector
|
||||
-- @since 1.31
|
||||
|
||||
local getmetatable = getmetatable
|
||||
local expect = dofile("rom/modules/main/cc/expect.lua").expect
|
||||
|
||||
local vmetatable
|
||||
|
||||
--- A 3-dimensional vector, with `x`, `y`, and `z` values.
|
||||
--
|
||||
-- This is suitable for representing both position and directional vectors.
|
||||
@ -27,6 +32,9 @@ local vector = {
|
||||
-- @usage v1:add(v2)
|
||||
-- @usage v1 + v2
|
||||
add = function(self, o)
|
||||
if getmetatable(self) ~= vmetatable then expect(1, self, "vector") end
|
||||
if getmetatable(o) ~= vmetatable then expect(2, o, "vector") end
|
||||
|
||||
return vector.new(
|
||||
self.x + o.x,
|
||||
self.y + o.y,
|
||||
@ -42,6 +50,9 @@ local vector = {
|
||||
-- @usage v1:sub(v2)
|
||||
-- @usage v1 - v2
|
||||
sub = function(self, o)
|
||||
if getmetatable(self) ~= vmetatable then expect(1, self, "vector") end
|
||||
if getmetatable(o) ~= vmetatable then expect(2, o, "vector") end
|
||||
|
||||
return vector.new(
|
||||
self.x - o.x,
|
||||
self.y - o.y,
|
||||
@ -52,30 +63,36 @@ local vector = {
|
||||
--- Multiplies a vector by a scalar value.
|
||||
--
|
||||
-- @tparam Vector self The vector to multiply.
|
||||
-- @tparam number m The scalar value to multiply with.
|
||||
-- @tparam number factor The scalar value to multiply with.
|
||||
-- @treturn Vector A vector with value `(x * m, y * m, z * m)`.
|
||||
-- @usage v:mul(3)
|
||||
-- @usage v * 3
|
||||
mul = function(self, m)
|
||||
-- @usage vector.new(1, 2, 3):mul(3)
|
||||
-- @usage vector.new(1, 2, 3) * 3
|
||||
mul = function(self, factor)
|
||||
if getmetatable(self) ~= vmetatable then expect(1, self, "vector") end
|
||||
expect(2, factor, "number")
|
||||
|
||||
return vector.new(
|
||||
self.x * m,
|
||||
self.y * m,
|
||||
self.z * m
|
||||
self.x * factor,
|
||||
self.y * factor,
|
||||
self.z * factor
|
||||
)
|
||||
end,
|
||||
|
||||
--- Divides a vector by a scalar value.
|
||||
--
|
||||
-- @tparam Vector self The vector to divide.
|
||||
-- @tparam number m The scalar value to divide by.
|
||||
-- @tparam number factor The scalar value to divide by.
|
||||
-- @treturn Vector A vector with value `(x / m, y / m, z / m)`.
|
||||
-- @usage v:div(3)
|
||||
-- @usage v / 3
|
||||
div = function(self, m)
|
||||
-- @usage vector.new(1, 2, 3):div(3)
|
||||
-- @usage vector.new(1, 2, 3) / 3
|
||||
div = function(self, factor)
|
||||
if getmetatable(self) ~= vmetatable then expect(1, self, "vector") end
|
||||
expect(2, factor, "number")
|
||||
|
||||
return vector.new(
|
||||
self.x / m,
|
||||
self.y / m,
|
||||
self.z / m
|
||||
self.x / factor,
|
||||
self.y / factor,
|
||||
self.z / factor
|
||||
)
|
||||
end,
|
||||
|
||||
@ -83,8 +100,9 @@ local vector = {
|
||||
--
|
||||
-- @tparam Vector self The vector to negate.
|
||||
-- @treturn Vector The negated vector.
|
||||
-- @usage -v
|
||||
-- @usage -vector.new(1, 2, 3)
|
||||
unm = function(self)
|
||||
if getmetatable(self) ~= vmetatable then expect(1, self, "vector") end
|
||||
return vector.new(
|
||||
-self.x,
|
||||
-self.y,
|
||||
@ -99,6 +117,9 @@ local vector = {
|
||||
-- @treturn Vector The dot product of `self` and `o`.
|
||||
-- @usage v1:dot(v2)
|
||||
dot = function(self, o)
|
||||
if getmetatable(self) ~= vmetatable then expect(1, self, "vector") end
|
||||
if getmetatable(o) ~= vmetatable then expect(2, o, "vector") end
|
||||
|
||||
return self.x * o.x + self.y * o.y + self.z * o.z
|
||||
end,
|
||||
|
||||
@ -109,6 +130,9 @@ local vector = {
|
||||
-- @treturn Vector The cross product of `self` and `o`.
|
||||
-- @usage v1:cross(v2)
|
||||
cross = function(self, o)
|
||||
if getmetatable(self) ~= vmetatable then expect(1, self, "vector") end
|
||||
if getmetatable(o) ~= vmetatable then expect(2, o, "vector") end
|
||||
|
||||
return vector.new(
|
||||
self.y * o.z - self.z * o.y,
|
||||
self.z * o.x - self.x * o.z,
|
||||
@ -120,6 +144,7 @@ local vector = {
|
||||
-- @tparam Vector self This vector.
|
||||
-- @treturn number The length of this vector.
|
||||
length = function(self)
|
||||
if getmetatable(self) ~= vmetatable then expect(1, self, "vector") end
|
||||
return math.sqrt(self.x * self.x + self.y * self.y + self.z * self.z)
|
||||
end,
|
||||
|
||||
@ -141,6 +166,9 @@ local vector = {
|
||||
-- nearest 0.5.
|
||||
-- @treturn Vector The rounded vector.
|
||||
round = function(self, tolerance)
|
||||
if getmetatable(self) ~= vmetatable then expect(1, self, "vector") end
|
||||
expect(2, tolerance, "number", "nil")
|
||||
|
||||
tolerance = tolerance or 1.0
|
||||
return vector.new(
|
||||
math.floor((self.x + tolerance * 0.5) / tolerance) * tolerance,
|
||||
@ -156,6 +184,8 @@ local vector = {
|
||||
-- @usage v:tostring()
|
||||
-- @usage tostring(v)
|
||||
tostring = function(self)
|
||||
if getmetatable(self) ~= vmetatable then expect(1, self, "vector") end
|
||||
|
||||
return self.x .. "," .. self.y .. "," .. self.z
|
||||
end,
|
||||
|
||||
@ -165,11 +195,15 @@ local vector = {
|
||||
-- @tparam Vector other The second vector to compare to.
|
||||
-- @treturn boolean Whether or not the vectors are equal.
|
||||
equals = function(self, other)
|
||||
if getmetatable(self) ~= vmetatable then expect(1, self, "vector") end
|
||||
if getmetatable(other) ~= vmetatable then expect(2, other, "vector") end
|
||||
|
||||
return self.x == other.x and self.y == other.y and self.z == other.z
|
||||
end,
|
||||
}
|
||||
|
||||
local vmetatable = {
|
||||
vmetatable = {
|
||||
__name = "vector",
|
||||
__index = vector,
|
||||
__add = vector.add,
|
||||
__sub = vector.sub,
|
||||
|
@ -1,3 +1,22 @@
|
||||
# New features in CC: Tweaked 1.110.2
|
||||
|
||||
* Add `speaker sound` command (fatboychummy).
|
||||
|
||||
Several bug fixes:
|
||||
* Improve error when calling `speaker play` with no path (fatboychummy).
|
||||
* Prevent playing music discs with `speaker.playSound`.
|
||||
* Various documentation fixes (cyberbit).
|
||||
* Fix generic peripherals not being able to transfer to some inventories on Forge.
|
||||
* Fix rare crash when holding a pocket computer.
|
||||
* Fix modems breaking when moved by Create.
|
||||
* Fix crash when rendering a turtle through an Immersive Portals portal.
|
||||
|
||||
# New features in CC: Tweaked 1.110.1
|
||||
|
||||
Several bug fixes:
|
||||
* Fix computers not turning on after they're unloaded/not-ticked for a while.
|
||||
* Fix networking cables sometimes not connecting on Forge.
|
||||
|
||||
# New features in CC: Tweaked 1.110.0
|
||||
|
||||
* Add a new `@c[...]` syntax for selecting computers in the `/computercraft` command.
|
||||
|
@ -1,16 +1,14 @@
|
||||
New features in CC: Tweaked 1.110.0
|
||||
New features in CC: Tweaked 1.110.2
|
||||
|
||||
* Add a new `@c[...]` syntax for selecting computers in the `/computercraft` command.
|
||||
* Remove custom breaking progress of modems on Forge.
|
||||
* Add `speaker sound` command (fatboychummy).
|
||||
|
||||
Several bug fixes:
|
||||
* Fix client and server DFPWM transcoders getting out of sync.
|
||||
* Fix `turtle.suck` reporting incorrect error when failing to suck items.
|
||||
* Fix pocket computers displaying state (blinking, modem light) for the wrong computer.
|
||||
* Fix crash when wrapping an invalid BE as a generic peripheral.
|
||||
* Chest peripherals now reattach when a chest is converted into a double chest.
|
||||
* Fix `speaker` program not resolving files relative to the current directory.
|
||||
* Skip main-thread tasks if the peripheral is detached.
|
||||
* Fix internal Lua VM errors if yielding inside `__tostring`.
|
||||
* Improve error when calling `speaker play` with no path (fatboychummy).
|
||||
* Prevent playing music discs with `speaker.playSound`.
|
||||
* Various documentation fixes (cyberbit).
|
||||
* Fix generic peripherals not being able to transfer to some inventories on Forge.
|
||||
* Fix rare crash when holding a pocket computer.
|
||||
* Fix modems breaking when moved by Create.
|
||||
* Fix crash when rendering a turtle through an Immersive Portals portal.
|
||||
|
||||
Type "help changelog" to see the full version history.
|
||||
|
@ -89,7 +89,7 @@ end
|
||||
--
|
||||
-- @tparam table image An image, as returned from [`load`] or [`parse`].
|
||||
-- @tparam number xPos The x position to start drawing at.
|
||||
-- @tparam number xPos The y position to start drawing at.
|
||||
-- @tparam number yPos The y position to start drawing at.
|
||||
-- @tparam[opt] term.Redirect target The terminal redirect to draw to. Defaults to the
|
||||
-- current terminal.
|
||||
local function draw(image, xPos, yPos, target)
|
||||
|
@ -43,6 +43,10 @@ if cmd == "stop" then
|
||||
for _, speaker in pairs(get_speakers(name)) do speaker.stop() end
|
||||
elseif cmd == "play" then
|
||||
local _, file, name = ...
|
||||
if not file then
|
||||
error("Usage: speaker play <file or url> [speaker]", 0)
|
||||
end
|
||||
|
||||
local speaker = get_speakers(name)[1]
|
||||
|
||||
local handle, err
|
||||
@ -128,9 +132,47 @@ elseif cmd == "play" then
|
||||
end
|
||||
|
||||
handle.close()
|
||||
elseif cmd == "sound" then
|
||||
local _, sound, volume, pitch, name = ...
|
||||
|
||||
if not sound then
|
||||
error("Usage: speaker sound <sound> [volume] [pitch] [speaker]", 0)
|
||||
return
|
||||
end
|
||||
|
||||
if volume then
|
||||
volume = tonumber(volume)
|
||||
if not volume then
|
||||
error("Volume must be a number", 0)
|
||||
end
|
||||
if volume < 0 or volume > 3 then
|
||||
error("Volume must be between 0 and 3", 0)
|
||||
end
|
||||
end
|
||||
|
||||
if pitch then
|
||||
pitch = tonumber(pitch)
|
||||
if not pitch then
|
||||
error("Pitch must be a number", 0)
|
||||
end
|
||||
if pitch < 0 or pitch > 2 then
|
||||
error("Pitch must be between 0 and 2", 0)
|
||||
end
|
||||
end
|
||||
|
||||
local speaker = get_speakers(name)[1]
|
||||
|
||||
if speaker.playSound(sound, volume, pitch) then
|
||||
print(("Played sound %q on speaker %q with volume %s and pitch %s."):format(
|
||||
sound, peripheral.getName(speaker), volume or 1, pitch or 1
|
||||
))
|
||||
else
|
||||
error(("Could not play sound %q"):format(sound), 0)
|
||||
end
|
||||
else
|
||||
local programName = arg[0] or fs.getName(shell.getRunningProgram())
|
||||
print("Usage:")
|
||||
print(programName .. " play <file or url> [speaker]")
|
||||
print(programName .. " sound <sound> [volume] [pitch] [speaker]")
|
||||
print(programName .. " stop [speaker]")
|
||||
end
|
||||
|
@ -117,7 +117,7 @@ shell.setCompletionFunction("rom/programs/fun/dj.lua", completion.build(
|
||||
completion.peripheral
|
||||
))
|
||||
shell.setCompletionFunction("rom/programs/fun/speaker.lua", completion.build(
|
||||
{ completion.choice, { "play ", "stop " } },
|
||||
{ completion.choice, { "play ", "sound ", "stop " } },
|
||||
function(shell, text, previous)
|
||||
if previous[2] == "play" then return completion.file(shell, text, previous, true)
|
||||
elseif previous[2] == "stop" then return completion.peripheral(shell, text, previous, false)
|
||||
|
@ -0,0 +1,71 @@
|
||||
-- SPDX-FileCopyrightText: 2024 The CC: Tweaked Developers
|
||||
--
|
||||
-- SPDX-License-Identifier: MPL-2.0
|
||||
|
||||
describe("The vector library", function()
|
||||
local vec = vector.new(1, 2, 3)
|
||||
|
||||
describe("vector.add", function()
|
||||
it("validates arguments", function()
|
||||
expect.error(vec.add, nil, vec):eq("bad argument #1 (vector expected, got nil)")
|
||||
expect.error(vec.add, vec, nil):eq("bad argument #2 (vector expected, got nil)")
|
||||
end)
|
||||
|
||||
it("returns the correct value", function()
|
||||
expect(vector.new(1, 2, 3) + vector.new(6, 4, 2)):eq(vector.new(7, 6, 5))
|
||||
end)
|
||||
end)
|
||||
|
||||
describe("vector.sub", function()
|
||||
it("validates arguments", function()
|
||||
expect.error(vec.sub, nil, vec):eq("bad argument #1 (vector expected, got nil)")
|
||||
expect.error(vec.sub, vec, nil):eq("bad argument #2 (vector expected, got nil)")
|
||||
end)
|
||||
|
||||
it("returns the correct value", function()
|
||||
expect(vector.new(6, 4, 2) - vector.new(1, 2, 3)):eq(vector.new(5, 2, -1))
|
||||
end)
|
||||
end)
|
||||
|
||||
describe("vector.mul", function()
|
||||
it("validates arguments", function()
|
||||
expect.error(vec.mul, nil, vec):eq("bad argument #1 (vector expected, got nil)")
|
||||
expect.error(vec.mul, vec, nil):eq("bad argument #2 (number expected, got nil)")
|
||||
end)
|
||||
|
||||
it("returns the correct value", function()
|
||||
expect(vector.new(1, 2, 3) * 2):eq(vector.new(2, 4, 6))
|
||||
end)
|
||||
end)
|
||||
|
||||
describe("vector.div", function()
|
||||
it("validates arguments", function()
|
||||
expect.error(vec.div, nil, vec):eq("bad argument #1 (vector expected, got nil)")
|
||||
expect.error(vec.div, vec, nil):eq("bad argument #2 (number expected, got nil)")
|
||||
end)
|
||||
|
||||
it("returns the correct value", function()
|
||||
expect(vector.new(1, 2, 3) / 2):eq(vector.new(0.5, 1, 1.5))
|
||||
end)
|
||||
end)
|
||||
|
||||
describe("vector.unm", function()
|
||||
it("validates arguments", function()
|
||||
expect.error(vec.unm, nil):eq("bad argument #1 (vector expected, got nil)")
|
||||
end)
|
||||
|
||||
it("returns the correct value", function()
|
||||
expect(-vector.new(2, 3, 6)):eq(vector.new(-2, -3, -6))
|
||||
end)
|
||||
end)
|
||||
|
||||
describe("vector.length", function()
|
||||
it("validates arguments", function()
|
||||
expect.error(vec.length, nil):eq("bad argument #1 (vector expected, got nil)")
|
||||
end)
|
||||
|
||||
it("returns the correct value", function()
|
||||
expect(vector.new(2, 3, 6):length()):eq(7)
|
||||
end)
|
||||
end)
|
||||
end)
|
@ -33,6 +33,8 @@ import net.minecraft.world.item.ItemStack;
|
||||
import net.minecraft.world.phys.BlockHitResult;
|
||||
import net.minecraft.world.phys.HitResult;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
import static dan200.computercraft.core.util.Nullability.assertNonNull;
|
||||
|
||||
public class ComputerCraftClient {
|
||||
@ -79,7 +81,7 @@ public class ComputerCraftClient {
|
||||
if (hit.getType() != HitResult.Type.BLOCK) return ItemStack.EMPTY;
|
||||
|
||||
var pos = ((BlockHitResult) hit).getBlockPos();
|
||||
var level = Minecraft.getInstance().level;
|
||||
var level = Objects.requireNonNull(Minecraft.getInstance().level);
|
||||
var state = level.getBlockState(pos);
|
||||
if (!(state.getBlock() instanceof CableBlock cable)) return ItemStack.EMPTY;
|
||||
|
||||
|
4
projects/fabric/src/generated/resources/data/create/tags/blocks/brittle.json
generated
Normal file
4
projects/fabric/src/generated/resources/data/create/tags/blocks/brittle.json
generated
Normal file
@ -0,0 +1,4 @@
|
||||
{
|
||||
"replace": false,
|
||||
"values": ["computercraft:cable", "computercraft:wireless_modem_normal", "computercraft:wireless_modem_advanced"]
|
||||
}
|
1
projects/forge/src/generated/resources/data/create/tags/blocks/brittle.json
generated
Normal file
1
projects/forge/src/generated/resources/data/create/tags/blocks/brittle.json
generated
Normal file
@ -0,0 +1 @@
|
||||
{"values": ["computercraft:cable", "computercraft:wireless_modem_normal", "computercraft:wireless_modem_advanced"]}
|
@ -10,6 +10,7 @@ import dan200.computercraft.api.detail.DetailRegistry;
|
||||
import dan200.computercraft.impl.detail.DetailRegistryImpl;
|
||||
import dan200.computercraft.shared.details.FluidData;
|
||||
import dan200.computercraft.shared.peripheral.generic.ComponentLookup;
|
||||
import dan200.computercraft.shared.util.CapabilityUtil;
|
||||
import net.minecraft.core.BlockPos;
|
||||
import net.minecraft.core.Direction;
|
||||
import net.minecraft.server.level.ServerLevel;
|
||||
@ -60,7 +61,7 @@ public final class ComputerCraftAPIImpl extends AbstractComputerCraftAPI impleme
|
||||
@Nullable
|
||||
@Override
|
||||
public T find(ServerLevel level, BlockPos pos, BlockState state, BlockEntity blockEntity, Direction side) {
|
||||
return level.getCapability(capability, pos, state, blockEntity, side);
|
||||
return CapabilityUtil.getCapability(level, capability, pos, state, blockEntity, side);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -8,6 +8,8 @@ import dan200.computercraft.api.detail.ForgeDetailRegistries;
|
||||
import dan200.computercraft.api.lua.LuaException;
|
||||
import dan200.computercraft.api.lua.LuaFunction;
|
||||
import dan200.computercraft.api.peripheral.IComputerAccess;
|
||||
import dan200.computercraft.api.peripheral.IPeripheral;
|
||||
import dan200.computercraft.shared.util.CapabilityUtil;
|
||||
import net.minecraft.core.registries.BuiltInRegistries;
|
||||
import net.minecraft.server.level.ServerLevel;
|
||||
import net.minecraft.world.level.block.entity.BlockEntity;
|
||||
@ -53,7 +55,7 @@ public final class FluidMethods extends AbstractFluidMethods<IFluidHandler> {
|
||||
var location = computer.getAvailablePeripheral(toName);
|
||||
if (location == null) throw new LuaException("Target '" + toName + "' does not exist");
|
||||
|
||||
var to = extractHandler(location.getTarget());
|
||||
var to = extractHandler(location);
|
||||
if (to == null) throw new LuaException("Target '" + toName + "' is not an tank");
|
||||
|
||||
int actualLimit = limit.orElse(Integer.MAX_VALUE);
|
||||
@ -78,7 +80,7 @@ public final class FluidMethods extends AbstractFluidMethods<IFluidHandler> {
|
||||
var location = computer.getAvailablePeripheral(fromName);
|
||||
if (location == null) throw new LuaException("Target '" + fromName + "' does not exist");
|
||||
|
||||
var from = extractHandler(location.getTarget());
|
||||
var from = extractHandler(location);
|
||||
if (from == null) throw new LuaException("Target '" + fromName + "' is not an tank");
|
||||
|
||||
int actualLimit = limit.orElse(Integer.MAX_VALUE);
|
||||
@ -90,14 +92,17 @@ public final class FluidMethods extends AbstractFluidMethods<IFluidHandler> {
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private static IFluidHandler extractHandler(@Nullable Object object) {
|
||||
private static IFluidHandler extractHandler(IPeripheral peripheral) {
|
||||
var object = peripheral.getTarget();
|
||||
var direction = peripheral instanceof dan200.computercraft.shared.peripheral.generic.GenericPeripheral sided ? sided.side() : null;
|
||||
|
||||
if (object instanceof BlockEntity blockEntity) {
|
||||
if (blockEntity.isRemoved()) return null;
|
||||
|
||||
var level = blockEntity.getLevel();
|
||||
if (!(level instanceof ServerLevel serverLevel)) return null;
|
||||
|
||||
var result = serverLevel.getCapability(Capabilities.FluidHandler.BLOCK, blockEntity.getBlockPos(), blockEntity.getBlockState(), blockEntity, null);
|
||||
var result = CapabilityUtil.getCapability(serverLevel, Capabilities.FluidHandler.BLOCK, blockEntity.getBlockPos(), blockEntity.getBlockState(), blockEntity, direction);
|
||||
if (result != null) return result;
|
||||
}
|
||||
|
||||
|
@ -8,7 +8,9 @@ import dan200.computercraft.api.detail.VanillaDetailRegistries;
|
||||
import dan200.computercraft.api.lua.LuaException;
|
||||
import dan200.computercraft.api.lua.LuaFunction;
|
||||
import dan200.computercraft.api.peripheral.IComputerAccess;
|
||||
import dan200.computercraft.api.peripheral.IPeripheral;
|
||||
import dan200.computercraft.shared.platform.ForgeContainerTransfer;
|
||||
import dan200.computercraft.shared.util.CapabilityUtil;
|
||||
import net.minecraft.server.level.ServerLevel;
|
||||
import net.minecraft.world.Container;
|
||||
import net.minecraft.world.level.block.entity.BlockEntity;
|
||||
@ -73,7 +75,7 @@ public final class InventoryMethods extends AbstractInventoryMethods<IItemHandle
|
||||
var location = computer.getAvailablePeripheral(toName);
|
||||
if (location == null) throw new LuaException("Target '" + toName + "' does not exist");
|
||||
|
||||
var to = extractHandler(location.getTarget());
|
||||
var to = extractHandler(location);
|
||||
if (to == null) throw new LuaException("Target '" + toName + "' is not an inventory");
|
||||
|
||||
// Validate slots
|
||||
@ -95,7 +97,7 @@ public final class InventoryMethods extends AbstractInventoryMethods<IItemHandle
|
||||
var location = computer.getAvailablePeripheral(fromName);
|
||||
if (location == null) throw new LuaException("Source '" + fromName + "' does not exist");
|
||||
|
||||
var from = extractHandler(location.getTarget());
|
||||
var from = extractHandler(location);
|
||||
if (from == null) throw new LuaException("Source '" + fromName + "' is not an inventory");
|
||||
|
||||
// Validate slots
|
||||
@ -108,14 +110,17 @@ public final class InventoryMethods extends AbstractInventoryMethods<IItemHandle
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private static IItemHandler extractHandler(@Nullable Object object) {
|
||||
private static IItemHandler extractHandler(IPeripheral peripheral) {
|
||||
var object = peripheral.getTarget();
|
||||
var direction = peripheral instanceof dan200.computercraft.shared.peripheral.generic.GenericPeripheral sided ? sided.side() : null;
|
||||
|
||||
if (object instanceof BlockEntity blockEntity) {
|
||||
if (blockEntity.isRemoved()) return null;
|
||||
|
||||
var level = blockEntity.getLevel();
|
||||
if (!(level instanceof ServerLevel serverLevel)) return null;
|
||||
|
||||
var result = serverLevel.getCapability(Capabilities.ItemHandler.BLOCK, blockEntity.getBlockPos(), blockEntity.getBlockState(), blockEntity, null);
|
||||
var result = CapabilityUtil.getCapability(serverLevel, Capabilities.ItemHandler.BLOCK, blockEntity.getBlockPos(), blockEntity.getBlockState(), blockEntity, direction);
|
||||
if (result != null) return result;
|
||||
}
|
||||
|
||||
|
@ -0,0 +1,36 @@
|
||||
// SPDX-FileCopyrightText: 2020 The CC: Tweaked Developers
|
||||
//
|
||||
// SPDX-License-Identifier: MPL-2.0
|
||||
|
||||
package dan200.computercraft.shared.util;
|
||||
|
||||
import net.minecraft.core.BlockPos;
|
||||
import net.minecraft.core.Direction;
|
||||
import net.minecraft.server.level.ServerLevel;
|
||||
import net.minecraft.world.level.block.entity.BlockEntity;
|
||||
import net.minecraft.world.level.block.state.BlockState;
|
||||
import net.neoforged.neoforge.capabilities.BlockCapability;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
public final class CapabilityUtil {
|
||||
private CapabilityUtil() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Find a capability, preferring the internal/null side but falling back to a given side if a mod doesn't support
|
||||
* the internal one.
|
||||
*
|
||||
* @param level The server level.
|
||||
* @param capability The capability to get.
|
||||
* @param pos The block position.
|
||||
* @param state The block state.
|
||||
* @param blockEntity The block entity.
|
||||
* @param side The side we'll fall back to.
|
||||
* @param <T> The type of the underlying capability.
|
||||
* @return The extracted capability, if present.
|
||||
*/
|
||||
public static <T> @Nullable T getCapability(ServerLevel level, BlockCapability<T, @Nullable Direction> capability, BlockPos pos, BlockState state, @Nullable BlockEntity blockEntity, @Nullable Direction side) {
|
||||
var cap = level.getCapability(capability, pos, state, blockEntity, null);
|
||||
return cap == null && side != null ? level.getCapability(capability, pos, state, blockEntity, side) : cap;
|
||||
}
|
||||
}
|
@ -7,6 +7,7 @@ package cc.tweaked.linter
|
||||
import com.google.common.collect.ImmutableSet
|
||||
import com.google.common.collect.ImmutableSetMultimap
|
||||
import com.uber.nullaway.LibraryModels
|
||||
import com.uber.nullaway.LibraryModels.FieldRef.fieldRef
|
||||
import com.uber.nullaway.LibraryModels.MethodRef.methodRef
|
||||
|
||||
/**
|
||||
@ -34,4 +35,9 @@ class MinecraftLibraryModel : LibraryModels {
|
||||
// Reasoning about nullability of BlockEntity.getLevel() is awkward. For now, assume it's non-null.
|
||||
methodRef("net.minecraft.world.level.block.entity.BlockEntity", "getLevel()"),
|
||||
)
|
||||
|
||||
override fun nullableFields(): ImmutableSet<LibraryModels.FieldRef> = ImmutableSet.of(
|
||||
// This inherits from Minecraft.hitResult, and so can also be null.
|
||||
fieldRef("net.minecraft.client.renderer.blockentity.BlockEntityRenderDispatcher", "cameraHitResult"),
|
||||
)
|
||||
}
|
||||
|
@ -5,11 +5,16 @@
|
||||
package cc.tweaked.standalone;
|
||||
|
||||
|
||||
import dan200.computercraft.api.lua.ILuaAPI;
|
||||
import dan200.computercraft.core.ComputerContext;
|
||||
import dan200.computercraft.core.CoreConfig;
|
||||
import dan200.computercraft.core.apis.IAPIEnvironment;
|
||||
import dan200.computercraft.core.apis.http.options.Action;
|
||||
import dan200.computercraft.core.apis.http.options.AddressRule;
|
||||
import dan200.computercraft.core.computer.Computer;
|
||||
import dan200.computercraft.core.filesystem.FileMount;
|
||||
import dan200.computercraft.core.filesystem.FileSystemException;
|
||||
import dan200.computercraft.core.filesystem.WritableFileMount;
|
||||
import dan200.computercraft.core.terminal.Terminal;
|
||||
import dan200.computercraft.core.terminal.TextBuffer;
|
||||
import dan200.computercraft.core.util.Colour;
|
||||
@ -31,6 +36,7 @@ import java.nio.ByteBuffer;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.nio.file.InvalidPathException;
|
||||
import java.nio.file.Path;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.OptionalInt;
|
||||
@ -55,37 +61,58 @@ public class Main {
|
||||
private static final Logger LOG = LoggerFactory.getLogger(Main.class);
|
||||
private static final boolean DEBUG = Checks.DEBUG;
|
||||
|
||||
private record TermSize(int width, int height) {
|
||||
public static final TermSize DEFAULT = new TermSize(51, 19);
|
||||
public static final Pattern PATTERN = Pattern.compile("^(\\d+)x(\\d+)$");
|
||||
}
|
||||
|
||||
private static <T> T getParsedOptionValue(CommandLine cli, Option opt, Class<T> klass) throws ParseException {
|
||||
var res = cli.getOptionValue(opt);
|
||||
if (klass == Path.class) {
|
||||
try {
|
||||
return klass.cast(Path.of(res));
|
||||
} catch (InvalidPathException e) {
|
||||
throw new ParseException("'" + res + "' is not a valid path (" + e.getReason() + ")");
|
||||
}
|
||||
} else if (klass == TermSize.class) {
|
||||
var matcher = TermSize.PATTERN.matcher(res);
|
||||
if (!matcher.matches()) throw new ParseException("'" + res + "' is not a valid terminal size.");
|
||||
|
||||
return klass.cast(new TermSize(Integer.parseInt(matcher.group(1)), Integer.parseInt(matcher.group(2))));
|
||||
} else {
|
||||
return klass.cast(TypeHandler.createValue(res, klass));
|
||||
private static Path parsePath(String path) throws ParseException {
|
||||
try {
|
||||
return Path.of(path);
|
||||
} catch (InvalidPathException e) {
|
||||
throw new ParseException("'" + path + "' is not a valid path (" + e.getReason() + ")");
|
||||
}
|
||||
}
|
||||
|
||||
private record TermSize(int width, int height) {
|
||||
public static final TermSize DEFAULT = new TermSize(51, 19);
|
||||
public static final Pattern PATTERN = Pattern.compile("^(\\d+)x(\\d+)$");
|
||||
|
||||
public static TermSize parse(String value) throws ParseException {
|
||||
var matcher = TermSize.PATTERN.matcher(value);
|
||||
if (!matcher.matches()) throw new ParseException("'" + value + "' is not a valid terminal size.");
|
||||
|
||||
return new TermSize(Integer.parseInt(matcher.group(1)), Integer.parseInt(matcher.group(2)));
|
||||
}
|
||||
}
|
||||
|
||||
private record MountPaths(Path src, String dest) {
|
||||
public static final Pattern PATTERN = Pattern.compile("^([^:]+):([^:]+)$");
|
||||
|
||||
public static MountPaths parse(String value) throws ParseException {
|
||||
var matcher = MountPaths.PATTERN.matcher(value);
|
||||
if (!matcher.matches()) throw new ParseException("'" + value + "' is not a mount spec.");
|
||||
|
||||
return new MountPaths(parsePath(matcher.group(1)), matcher.group(2));
|
||||
}
|
||||
}
|
||||
|
||||
private interface ValueParser<T> {
|
||||
T parse(String path) throws ParseException;
|
||||
}
|
||||
|
||||
@Contract("_, _, _, !null -> !null")
|
||||
private static <T> @Nullable T getParsedOptionValue(CommandLine cli, Option opt, Class<T> klass, @Nullable T defaultValue) throws ParseException {
|
||||
return cli.hasOption(opt) ? getParsedOptionValue(cli, opt, klass) : defaultValue;
|
||||
private static <T> @Nullable T getParsedOptionValue(CommandLine cli, Option opt, ValueParser<T> parser, @Nullable T defaultValue) throws ParseException {
|
||||
return cli.hasOption(opt) ? parser.parse(cli.getOptionValue(opt)) : defaultValue;
|
||||
}
|
||||
|
||||
private static <T> List<T> getParsedOptionValues(CommandLine cli, Option opt, ValueParser<T> parser) throws ParseException {
|
||||
var values = cli.getOptionValues(opt);
|
||||
if (values == null) return List.of();
|
||||
|
||||
List<T> parsedValues = new ArrayList<>(values.length);
|
||||
for (var value : values) parsedValues.add(parser.parse(value));
|
||||
return List.copyOf(parsedValues);
|
||||
}
|
||||
|
||||
public static void main(String[] args) throws InterruptedException {
|
||||
var options = new Options();
|
||||
Option resourceOpt, computerOpt, termSizeOpt, allowLocalDomainsOpt, helpOpt;
|
||||
Option resourceOpt, computerOpt, termSizeOpt, allowLocalDomainsOpt, helpOpt, mountOpt, mountRoOpt;
|
||||
options.addOption(resourceOpt = Option.builder("r").argName("PATH").longOpt("resources").hasArg()
|
||||
.desc("The path to the resources directory")
|
||||
.build());
|
||||
@ -98,6 +125,12 @@ public class Main {
|
||||
options.addOption(allowLocalDomainsOpt = Option.builder("L").longOpt("allow-local-domains")
|
||||
.desc("Allow accessing local domains with the HTTP API.")
|
||||
.build());
|
||||
options.addOption(mountOpt = Option.builder().longOpt("mount").hasArg().argName("SRC:DEST")
|
||||
.desc("Mount a folder SRC at directory DEST on the computer.")
|
||||
.build());
|
||||
options.addOption(mountRoOpt = Option.builder().longOpt("mount-ro").hasArg().argName("SRC:DEST")
|
||||
.desc("Mount a read-only folder SRC at directory DEST on the computer.")
|
||||
.build());
|
||||
|
||||
options.addOption(helpOpt = Option.builder("h").longOpt("help")
|
||||
.desc("Print help message")
|
||||
@ -107,6 +140,7 @@ public class Main {
|
||||
Path computerDirectory;
|
||||
TermSize termSize;
|
||||
boolean allowLocalDomains;
|
||||
List<MountPaths> mounts, readOnlyMounts;
|
||||
try {
|
||||
var cli = new DefaultParser().parse(options, args);
|
||||
if (cli.hasOption(helpOpt)) {
|
||||
@ -115,10 +149,12 @@ public class Main {
|
||||
}
|
||||
if (!cli.hasOption(resourceOpt)) throw new ParseException("--resources directory is required");
|
||||
|
||||
resourcesDirectory = getParsedOptionValue(cli, resourceOpt, Path.class);
|
||||
computerDirectory = getParsedOptionValue(cli, computerOpt, Path.class, null);
|
||||
termSize = getParsedOptionValue(cli, termSizeOpt, TermSize.class, TermSize.DEFAULT);
|
||||
resourcesDirectory = parsePath(cli.getOptionValue(resourceOpt));
|
||||
computerDirectory = getParsedOptionValue(cli, computerOpt, Main::parsePath, null);
|
||||
termSize = getParsedOptionValue(cli, termSizeOpt, TermSize::parse, TermSize.DEFAULT);
|
||||
allowLocalDomains = cli.hasOption(allowLocalDomainsOpt);
|
||||
mounts = getParsedOptionValues(cli, mountOpt, MountPaths::parse);
|
||||
readOnlyMounts = getParsedOptionValues(cli, mountRoOpt, MountPaths::parse);
|
||||
} catch (ParseException e) {
|
||||
System.err.println(e.getLocalizedMessage());
|
||||
|
||||
@ -143,6 +179,7 @@ public class Main {
|
||||
new Terminal(termSize.width(), termSize.height(), true, () -> isDirty.set(true)),
|
||||
0
|
||||
);
|
||||
computer.addApi(new FileMounter(computer.getAPIEnvironment(), readOnlyMounts, mounts));
|
||||
computer.turnOn();
|
||||
|
||||
runAndInit(gl, computer, isDirty);
|
||||
@ -154,6 +191,41 @@ public class Main {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* An {@link ILuaAPI} which is used to mount additional files, but does not expose any new globals/methods.
|
||||
*/
|
||||
private static final class FileMounter implements ILuaAPI {
|
||||
private final IAPIEnvironment environment;
|
||||
private final List<MountPaths> readOnlyMounts;
|
||||
private final List<MountPaths> mounts;
|
||||
|
||||
FileMounter(IAPIEnvironment environment, List<MountPaths> readOnlyMounts, List<MountPaths> mounts) {
|
||||
this.environment = environment;
|
||||
this.readOnlyMounts = readOnlyMounts;
|
||||
this.mounts = mounts;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String[] getNames() {
|
||||
return new String[0];
|
||||
}
|
||||
|
||||
@Override
|
||||
public void startup() {
|
||||
try {
|
||||
var fs = environment.getFileSystem();
|
||||
for (var mount : readOnlyMounts) {
|
||||
fs.mount(mount.dest(), mount.dest(), new FileMount(mount.src()));
|
||||
}
|
||||
for (var mount : mounts) {
|
||||
fs.mount(mount.dest(), mount.dest(), new WritableFileMount(mount.src().toFile(), 1_000_000));
|
||||
}
|
||||
} catch (FileSystemException e) {
|
||||
throw new IllegalStateException(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static final int SCALE = 2;
|
||||
private static final int MARGIN = 2;
|
||||
private static final int PIXEL_WIDTH = 6;
|
||||
|
Loading…
Reference in New Issue
Block a user