mirror of
https://github.com/SquidDev-CC/CC-Tweaked
synced 2025-10-18 15:37:38 +00:00
Compare commits
32 Commits
v1.21-1.11
...
v1.21.1-1.
Author | SHA1 | Date | |
---|---|---|---|
![]() |
0d8ac304c7 | ||
![]() |
fdd5f49369 | ||
![]() |
d24984c1d5 | ||
![]() |
8080dcdd9e | ||
![]() |
d7cea55e2a | ||
![]() |
9b2f974a81 | ||
![]() |
43770fa9bd | ||
![]() |
80c7a54ad4 | ||
![]() |
e57b6fede2 | ||
![]() |
34a2fd039f | ||
![]() |
3299d0e72a | ||
![]() |
b89e2615db | ||
![]() |
cdcd82679c | ||
![]() |
cdfa866760 | ||
![]() |
aa8078ddeb | ||
![]() |
7e53c19d74 | ||
![]() |
b7a8432cfb | ||
![]() |
356c8e8aeb | ||
![]() |
ed283155f7 | ||
![]() |
87dfad026e | ||
![]() |
bb97c465d9 | ||
![]() |
8bd4c3370e | ||
![]() |
3eb84ffedd | ||
![]() |
9484315d37 | ||
![]() |
be59f1a875 | ||
![]() |
bfb28b4710 | ||
![]() |
216f0adb3c | ||
![]() |
dad6874638 | ||
![]() |
77af4bc213 | ||
![]() |
5abab982c7 | ||
![]() |
764e1aa332 | ||
![]() |
c47718b09d |
@@ -101,7 +101,7 @@ SPDX-License-Identifier = "CC0-1.0"
|
|||||||
path = ".github/**"
|
path = ".github/**"
|
||||||
|
|
||||||
[[annotations]]
|
[[annotations]]
|
||||||
path = ["gradle/wrapper/**", "gradlew", "gradlew.bat"]
|
path = ["gradle/wrapper/**"]
|
||||||
SPDX-FileCopyrightText = "Gradle Inc"
|
SPDX-FileCopyrightText = "Gradle Inc"
|
||||||
SPDX-License-Identifier = "Apache-2.0"
|
SPDX-License-Identifier = "Apache-2.0"
|
||||||
|
|
||||||
|
@@ -159,7 +159,7 @@ fun getNextVersion(version: String): String {
|
|||||||
val lastIndex = mainVersion.lastIndexOf('.')
|
val lastIndex = mainVersion.lastIndexOf('.')
|
||||||
if (lastIndex < 0) throw IllegalArgumentException("Cannot parse version format \"$version\"")
|
if (lastIndex < 0) throw IllegalArgumentException("Cannot parse version format \"$version\"")
|
||||||
val lastVersion = try {
|
val lastVersion = try {
|
||||||
version.substring(lastIndex + 1).toInt()
|
mainVersion.substring(lastIndex + 1).toInt()
|
||||||
} catch (e: NumberFormatException) {
|
} catch (e: NumberFormatException) {
|
||||||
throw IllegalArgumentException("Cannot parse version format \"$version\"", e)
|
throw IllegalArgumentException("Cannot parse version format \"$version\"", e)
|
||||||
}
|
}
|
||||||
|
@@ -12,7 +12,7 @@ neogradle.subsystems.conventions.runs.enabled=false
|
|||||||
|
|
||||||
# Mod properties
|
# Mod properties
|
||||||
isUnstable=true
|
isUnstable=true
|
||||||
modVersion=1.112.0
|
modVersion=1.113.0
|
||||||
|
|
||||||
# Minecraft properties: We want to configure this here so we can read it in settings.gradle
|
# Minecraft properties: We want to configure this here so we can read it in settings.gradle
|
||||||
mcVersion=1.21
|
mcVersion=1.21.1
|
||||||
|
@@ -7,14 +7,14 @@
|
|||||||
# Minecraft
|
# Minecraft
|
||||||
# MC version is specified in gradle.properties, as we need that in settings.gradle.
|
# MC version is specified in gradle.properties, as we need that in settings.gradle.
|
||||||
# Remember to update corresponding versions in fabric.mod.json/neoforge.mods.toml
|
# Remember to update corresponding versions in fabric.mod.json/neoforge.mods.toml
|
||||||
fabric-api = "0.100.7+1.21"
|
fabric-api = "0.102.1+1.21.1"
|
||||||
fabric-loader = "0.15.11"
|
fabric-loader = "0.15.11"
|
||||||
neoForge = "21.0.143"
|
neoForge = "21.1.9"
|
||||||
neoForgeSpi = "8.0.1"
|
neoForgeSpi = "8.0.1"
|
||||||
mixin = "0.8.5"
|
mixin = "0.8.5"
|
||||||
parchment = "2024.07.28"
|
parchment = "2024.07.28"
|
||||||
parchmentMc = "1.21"
|
parchmentMc = "1.21"
|
||||||
yarn = "1.21+build.1"
|
yarn = "1.21.1+build.1"
|
||||||
|
|
||||||
# Core dependencies (these versions are tied to the version Minecraft uses)
|
# Core dependencies (these versions are tied to the version Minecraft uses)
|
||||||
fastutil = "8.5.12"
|
fastutil = "8.5.12"
|
||||||
@@ -39,7 +39,7 @@ nightConfig = "3.6.7"
|
|||||||
emi = "1.1.7+1.21"
|
emi = "1.1.7+1.21"
|
||||||
fabricPermissions = "0.3.1"
|
fabricPermissions = "0.3.1"
|
||||||
iris = "1.6.14+1.20.4"
|
iris = "1.6.14+1.20.4"
|
||||||
jei = "19.0.0.1"
|
jei = "19.8.2.99"
|
||||||
modmenu = "11.0.0-rc.4"
|
modmenu = "11.0.0-rc.4"
|
||||||
moreRed = "4.0.0.4"
|
moreRed = "4.0.0.4"
|
||||||
oculus = "1.2.5"
|
oculus = "1.2.5"
|
||||||
|
BIN
gradle/wrapper/gradle-wrapper.jar
vendored
BIN
gradle/wrapper/gradle-wrapper.jar
vendored
Binary file not shown.
2
gradle/wrapper/gradle-wrapper.properties
vendored
2
gradle/wrapper/gradle-wrapper.properties
vendored
@@ -1,6 +1,6 @@
|
|||||||
distributionBase=GRADLE_USER_HOME
|
distributionBase=GRADLE_USER_HOME
|
||||||
distributionPath=wrapper/dists
|
distributionPath=wrapper/dists
|
||||||
distributionUrl=https\://services.gradle.org/distributions/gradle-8.9-bin.zip
|
distributionUrl=https\://services.gradle.org/distributions/gradle-8.10-bin.zip
|
||||||
networkTimeout=10000
|
networkTimeout=10000
|
||||||
validateDistributionUrl=true
|
validateDistributionUrl=true
|
||||||
zipStoreBase=GRADLE_USER_HOME
|
zipStoreBase=GRADLE_USER_HOME
|
||||||
|
5
gradlew
vendored
5
gradlew
vendored
@@ -15,6 +15,8 @@
|
|||||||
# See the License for the specific language governing permissions and
|
# See the License for the specific language governing permissions and
|
||||||
# limitations under the License.
|
# limitations under the License.
|
||||||
#
|
#
|
||||||
|
# SPDX-License-Identifier: Apache-2.0
|
||||||
|
#
|
||||||
|
|
||||||
##############################################################################
|
##############################################################################
|
||||||
#
|
#
|
||||||
@@ -84,7 +86,8 @@ done
|
|||||||
# shellcheck disable=SC2034
|
# shellcheck disable=SC2034
|
||||||
APP_BASE_NAME=${0##*/}
|
APP_BASE_NAME=${0##*/}
|
||||||
# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036)
|
# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036)
|
||||||
APP_HOME=$( cd "${APP_HOME:-./}" > /dev/null && pwd -P ) || exit
|
APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s
|
||||||
|
' "$PWD" ) || exit
|
||||||
|
|
||||||
# Use the maximum available, or set MAX_FD != -1 to use that value.
|
# Use the maximum available, or set MAX_FD != -1 to use that value.
|
||||||
MAX_FD=maximum
|
MAX_FD=maximum
|
||||||
|
2
gradlew.bat
vendored
2
gradlew.bat
vendored
@@ -13,6 +13,8 @@
|
|||||||
@rem See the License for the specific language governing permissions and
|
@rem See the License for the specific language governing permissions and
|
||||||
@rem limitations under the License.
|
@rem limitations under the License.
|
||||||
@rem
|
@rem
|
||||||
|
@rem SPDX-License-Identifier: Apache-2.0
|
||||||
|
@rem
|
||||||
|
|
||||||
@if "%DEBUG%"=="" @echo off
|
@if "%DEBUG%"=="" @echo off
|
||||||
@rem ##########################################################################
|
@rem ##########################################################################
|
||||||
|
@@ -13,7 +13,7 @@ import java.util.Map;
|
|||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* An item detail provider for {@link ItemStack}'s whose {@link Item} has a specific type.
|
* An item detail provider for {@link ItemStack}s whose {@link Item} has a specific type.
|
||||||
*
|
*
|
||||||
* @param <T> The type the stack's item must have.
|
* @param <T> The type the stack's item must have.
|
||||||
*/
|
*/
|
||||||
@@ -22,7 +22,7 @@ public abstract class BasicItemDetailProvider<T> implements DetailProvider<ItemS
|
|||||||
private final @Nullable String namespace;
|
private final @Nullable String namespace;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create a new item detail provider. Meta will be inserted into a new sub-map named as per {@code namespace}.
|
* Create a new item detail provider. Details will be inserted into a new sub-map named as per {@code namespace}.
|
||||||
*
|
*
|
||||||
* @param itemType The type the stack's item must have.
|
* @param itemType The type the stack's item must have.
|
||||||
* @param namespace The namespace to use for this provider.
|
* @param namespace The namespace to use for this provider.
|
||||||
@@ -34,7 +34,7 @@ public abstract class BasicItemDetailProvider<T> implements DetailProvider<ItemS
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create a new item detail provider. Meta will be inserted directly into the results.
|
* Create a new item detail provider. Details will be inserted directly into the results.
|
||||||
*
|
*
|
||||||
* @param itemType The type the stack's item must have.
|
* @param itemType The type the stack's item must have.
|
||||||
*/
|
*/
|
||||||
@@ -53,21 +53,18 @@ public abstract class BasicItemDetailProvider<T> implements DetailProvider<ItemS
|
|||||||
* @param stack The item stack to provide details for.
|
* @param stack The item stack to provide details for.
|
||||||
* @param item The item to provide details for.
|
* @param item The item to provide details for.
|
||||||
*/
|
*/
|
||||||
public abstract void provideDetails(
|
public abstract void provideDetails(Map<? super String, Object> data, ItemStack stack, T item);
|
||||||
Map<? super String, Object> data, ItemStack stack, T item
|
|
||||||
);
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void provideDetails(Map<? super String, Object> data, ItemStack stack) {
|
public final void provideDetails(Map<? super String, Object> data, ItemStack stack) {
|
||||||
var item = stack.getItem();
|
var item = stack.getItem();
|
||||||
if (!itemType.isInstance(item)) return;
|
if (!itemType.isInstance(item)) return;
|
||||||
|
|
||||||
// If `namespace` is specified, insert into a new data map instead of the existing one.
|
if (namespace == null) {
|
||||||
Map<? super String, Object> child = namespace == null ? data : new HashMap<>();
|
provideDetails(data, stack, itemType.cast(item));
|
||||||
|
} else {
|
||||||
provideDetails(child, stack, itemType.cast(item));
|
Map<? super String, Object> child = new HashMap<>();
|
||||||
|
provideDetails(child, stack, itemType.cast(item));
|
||||||
if (namespace != null) {
|
|
||||||
data.put(namespace, child);
|
data.put(namespace, child);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -0,0 +1,72 @@
|
|||||||
|
// SPDX-FileCopyrightText: 2024 The CC: Tweaked Developers
|
||||||
|
//
|
||||||
|
// SPDX-License-Identifier: MPL-2.0
|
||||||
|
|
||||||
|
package dan200.computercraft.api.detail;
|
||||||
|
|
||||||
|
import net.minecraft.core.component.DataComponentHolder;
|
||||||
|
import net.minecraft.core.component.DataComponentType;
|
||||||
|
import net.minecraft.world.item.ItemStack;
|
||||||
|
|
||||||
|
import javax.annotation.Nullable;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Objects;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An item detail provider for a specific {@linkplain DataComponentType data component} on {@link ItemStack}s or
|
||||||
|
* other {@link DataComponentHolder}.
|
||||||
|
*
|
||||||
|
* @param <T> The type of the component's contents.
|
||||||
|
*/
|
||||||
|
public abstract class ComponentDetailProvider<T> implements DetailProvider<DataComponentHolder> {
|
||||||
|
private final DataComponentType<T> component;
|
||||||
|
private final @Nullable String namespace;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a new component detail provider. Details will be inserted into a new sub-map named as per {@code namespace}.
|
||||||
|
*
|
||||||
|
* @param component The data component to provide details for.
|
||||||
|
* @param namespace The namespace to use for this provider.
|
||||||
|
*/
|
||||||
|
public ComponentDetailProvider(@Nullable String namespace, DataComponentType<T> component) {
|
||||||
|
Objects.requireNonNull(component);
|
||||||
|
this.component = component;
|
||||||
|
this.namespace = namespace;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a new component detail provider. Details will be inserted directly into the results.
|
||||||
|
*
|
||||||
|
* @param component The data component to provide details for.
|
||||||
|
*/
|
||||||
|
public ComponentDetailProvider(DataComponentType<T> component) {
|
||||||
|
this(null, component);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Provide additional details for the given data component. This method is called by {@code turtle.getItemDetail()}.
|
||||||
|
* New properties should be added to the given {@link Map}, {@code data}.
|
||||||
|
* <p>
|
||||||
|
* This method is always called on the server thread, so it is safe to interact with the world here, but you should
|
||||||
|
* take care to avoid long blocking operations as this will stall the server and other computers.
|
||||||
|
*
|
||||||
|
* @param data The full details to be returned for this item stack. New properties should be added to this map.
|
||||||
|
* @param item The component to provide details for.
|
||||||
|
*/
|
||||||
|
public abstract void provideComponentDetails(Map<? super String, Object> data, T item);
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public final void provideDetails(Map<? super String, Object> data, DataComponentHolder holder) {
|
||||||
|
var value = holder.get(component);
|
||||||
|
if (value == null) return;
|
||||||
|
|
||||||
|
if (namespace == null) {
|
||||||
|
provideComponentDetails(data, value);
|
||||||
|
} else {
|
||||||
|
Map<? super String, Object> child = new HashMap<>();
|
||||||
|
provideComponentDetails(child, value);
|
||||||
|
data.put(namespace, child);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@@ -26,7 +26,7 @@ public interface DetailRegistry<T> {
|
|||||||
* @param provider The detail provider to register.
|
* @param provider The detail provider to register.
|
||||||
* @see DetailProvider
|
* @see DetailProvider
|
||||||
*/
|
*/
|
||||||
void addProvider(DetailProvider<T> provider);
|
void addProvider(DetailProvider<? super T> provider);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Compute basic details about an object. This is cheaper than computing all details operation, and so is suitable
|
* Compute basic details about an object. This is cheaper than computing all details operation, and so is suitable
|
||||||
|
@@ -5,7 +5,7 @@
|
|||||||
package dan200.computercraft.api.turtle;
|
package dan200.computercraft.api.turtle;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* An enum representing the two sides of the turtle that a turtle turtle might reside.
|
* An enum representing the two sides of the turtle that a turtle upgrade might reside.
|
||||||
*/
|
*/
|
||||||
public enum TurtleSide {
|
public enum TurtleSide {
|
||||||
/**
|
/**
|
||||||
|
@@ -13,6 +13,7 @@ import dan200.computercraft.api.client.turtle.RegisterTurtleUpgradeModeller;
|
|||||||
import dan200.computercraft.api.client.turtle.TurtleUpgradeModeller;
|
import dan200.computercraft.api.client.turtle.TurtleUpgradeModeller;
|
||||||
import dan200.computercraft.client.gui.*;
|
import dan200.computercraft.client.gui.*;
|
||||||
import dan200.computercraft.client.pocket.ClientPocketComputers;
|
import dan200.computercraft.client.pocket.ClientPocketComputers;
|
||||||
|
import dan200.computercraft.client.render.CustomLecternRenderer;
|
||||||
import dan200.computercraft.client.render.RenderTypes;
|
import dan200.computercraft.client.render.RenderTypes;
|
||||||
import dan200.computercraft.client.render.TurtleBlockEntityRenderer;
|
import dan200.computercraft.client.render.TurtleBlockEntityRenderer;
|
||||||
import dan200.computercraft.client.render.monitor.MonitorBlockEntityRenderer;
|
import dan200.computercraft.client.render.monitor.MonitorBlockEntityRenderer;
|
||||||
@@ -79,6 +80,7 @@ public final class ClientRegistry {
|
|||||||
BlockEntityRenderers.register(ModRegistry.BlockEntities.MONITOR_ADVANCED.get(), MonitorBlockEntityRenderer::new);
|
BlockEntityRenderers.register(ModRegistry.BlockEntities.MONITOR_ADVANCED.get(), MonitorBlockEntityRenderer::new);
|
||||||
BlockEntityRenderers.register(ModRegistry.BlockEntities.TURTLE_NORMAL.get(), TurtleBlockEntityRenderer::new);
|
BlockEntityRenderers.register(ModRegistry.BlockEntities.TURTLE_NORMAL.get(), TurtleBlockEntityRenderer::new);
|
||||||
BlockEntityRenderers.register(ModRegistry.BlockEntities.TURTLE_ADVANCED.get(), TurtleBlockEntityRenderer::new);
|
BlockEntityRenderers.register(ModRegistry.BlockEntities.TURTLE_ADVANCED.get(), TurtleBlockEntityRenderer::new);
|
||||||
|
BlockEntityRenderers.register(ModRegistry.BlockEntities.LECTERN.get(), CustomLecternRenderer::new);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@@ -6,15 +6,19 @@ package dan200.computercraft.client.gui;
|
|||||||
|
|
||||||
import dan200.computercraft.core.terminal.TextBuffer;
|
import dan200.computercraft.core.terminal.TextBuffer;
|
||||||
import dan200.computercraft.shared.ModRegistry;
|
import dan200.computercraft.shared.ModRegistry;
|
||||||
import dan200.computercraft.shared.common.HeldItemMenu;
|
import dan200.computercraft.shared.media.PrintoutMenu;
|
||||||
import dan200.computercraft.shared.media.items.PrintoutData;
|
import dan200.computercraft.shared.media.items.PrintoutData;
|
||||||
import dan200.computercraft.shared.media.items.PrintoutItem;
|
|
||||||
import net.minecraft.client.gui.GuiGraphics;
|
import net.minecraft.client.gui.GuiGraphics;
|
||||||
import net.minecraft.client.gui.screens.inventory.AbstractContainerScreen;
|
import net.minecraft.client.gui.screens.inventory.AbstractContainerScreen;
|
||||||
import net.minecraft.network.chat.Component;
|
import net.minecraft.network.chat.Component;
|
||||||
import net.minecraft.world.entity.player.Inventory;
|
import net.minecraft.world.entity.player.Inventory;
|
||||||
|
import net.minecraft.world.inventory.AbstractContainerMenu;
|
||||||
|
import net.minecraft.world.inventory.ContainerListener;
|
||||||
|
import net.minecraft.world.item.ItemStack;
|
||||||
import org.lwjgl.glfw.GLFW;
|
import org.lwjgl.glfw.GLFW;
|
||||||
|
|
||||||
|
import java.util.Objects;
|
||||||
|
|
||||||
import static dan200.computercraft.client.render.PrintoutRenderer.*;
|
import static dan200.computercraft.client.render.PrintoutRenderer.*;
|
||||||
import static dan200.computercraft.client.render.RenderTypes.FULL_BRIGHT_LIGHTMAP;
|
import static dan200.computercraft.client.render.RenderTypes.FULL_BRIGHT_LIGHTMAP;
|
||||||
|
|
||||||
@@ -23,41 +27,65 @@ import static dan200.computercraft.client.render.RenderTypes.FULL_BRIGHT_LIGHTMA
|
|||||||
*
|
*
|
||||||
* @see dan200.computercraft.client.render.PrintoutRenderer
|
* @see dan200.computercraft.client.render.PrintoutRenderer
|
||||||
*/
|
*/
|
||||||
public class PrintoutScreen extends AbstractContainerScreen<HeldItemMenu> {
|
public final class PrintoutScreen extends AbstractContainerScreen<PrintoutMenu> implements ContainerListener {
|
||||||
private final boolean book;
|
private PrintoutInfo printout = PrintoutInfo.DEFAULT;
|
||||||
private final int pages;
|
private int page = 0;
|
||||||
private final TextBuffer[] text;
|
|
||||||
private final TextBuffer[] colours;
|
|
||||||
private int page;
|
|
||||||
|
|
||||||
public PrintoutScreen(HeldItemMenu container, Inventory player, Component title) {
|
public PrintoutScreen(PrintoutMenu container, Inventory player, Component title) {
|
||||||
super(container, player, title);
|
super(container, player, title);
|
||||||
|
|
||||||
imageHeight = Y_SIZE;
|
imageHeight = Y_SIZE;
|
||||||
|
}
|
||||||
|
|
||||||
var printout = container.getStack().getOrDefault(ModRegistry.DataComponents.PRINTOUT.get(), PrintoutData.EMPTY);
|
private void setPrintout(ItemStack stack) {
|
||||||
this.text = new TextBuffer[printout.lines().size()];
|
|
||||||
this.colours = new TextBuffer[printout.lines().size()];
|
|
||||||
for (var i = 0; i < this.text.length; i++) {
|
|
||||||
var line = printout.lines().get(i);
|
|
||||||
this.text[i] = new TextBuffer(line.text());
|
|
||||||
this.colours[i] = new TextBuffer(line.foreground());
|
|
||||||
}
|
|
||||||
|
|
||||||
page = 0;
|
page = 0;
|
||||||
pages = Math.max(this.text.length / PrintoutData.LINES_PER_PAGE, 1);
|
printout = PrintoutInfo.of(PrintoutData.getOrEmpty(stack), stack.is(ModRegistry.Items.PRINTED_BOOK.get()));
|
||||||
book = ((PrintoutItem) container.getStack().getItem()).getType() == PrintoutItem.Type.BOOK;
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void init() {
|
||||||
|
super.init();
|
||||||
|
menu.addSlotListener(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void removed() {
|
||||||
|
menu.removeSlotListener(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void slotChanged(AbstractContainerMenu menu, int slot, ItemStack stack) {
|
||||||
|
if (slot == 0) setPrintout(stack);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void dataChanged(AbstractContainerMenu menu, int slot, int data) {
|
||||||
|
if (slot == PrintoutMenu.DATA_CURRENT_PAGE) page = data;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void setPage(int page) {
|
||||||
|
this.page = page;
|
||||||
|
|
||||||
|
var gameMode = Objects.requireNonNull(Objects.requireNonNull(minecraft).gameMode);
|
||||||
|
gameMode.handleInventoryButtonClick(menu.containerId, PrintoutMenu.PAGE_BUTTON_OFFSET + page);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void previousPage() {
|
||||||
|
if (page > 0) setPage(page - 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void nextPage() {
|
||||||
|
if (page < printout.pages() - 1) setPage(page + 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean keyPressed(int key, int scancode, int modifiers) {
|
public boolean keyPressed(int key, int scancode, int modifiers) {
|
||||||
if (key == GLFW.GLFW_KEY_RIGHT) {
|
if (key == GLFW.GLFW_KEY_RIGHT) {
|
||||||
if (page < pages - 1) page++;
|
nextPage();
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (key == GLFW.GLFW_KEY_LEFT) {
|
if (key == GLFW.GLFW_KEY_LEFT) {
|
||||||
if (page > 0) page--;
|
previousPage();
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -69,13 +97,13 @@ public class PrintoutScreen extends AbstractContainerScreen<HeldItemMenu> {
|
|||||||
if (super.mouseScrolled(x, y, deltaX, deltaY)) return true;
|
if (super.mouseScrolled(x, y, deltaX, deltaY)) return true;
|
||||||
if (deltaY < 0) {
|
if (deltaY < 0) {
|
||||||
// Scroll up goes to the next page
|
// Scroll up goes to the next page
|
||||||
if (page < pages - 1) page++;
|
nextPage();
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (deltaY > 0) {
|
if (deltaY > 0) {
|
||||||
// Scroll down goes to the previous page
|
// Scroll down goes to the previous page
|
||||||
if (page > 0) page--;
|
previousPage();
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -88,8 +116,8 @@ public class PrintoutScreen extends AbstractContainerScreen<HeldItemMenu> {
|
|||||||
graphics.pose().pushPose();
|
graphics.pose().pushPose();
|
||||||
graphics.pose().translate(0, 0, 1);
|
graphics.pose().translate(0, 0, 1);
|
||||||
|
|
||||||
drawBorder(graphics.pose(), graphics.bufferSource(), leftPos, topPos, 0, page, pages, book, FULL_BRIGHT_LIGHTMAP);
|
drawBorder(graphics.pose(), graphics.bufferSource(), leftPos, topPos, 0, page, printout.pages(), printout.book(), FULL_BRIGHT_LIGHTMAP);
|
||||||
drawText(graphics.pose(), graphics.bufferSource(), leftPos + X_TEXT_MARGIN, topPos + Y_TEXT_MARGIN, PrintoutData.LINES_PER_PAGE * page, FULL_BRIGHT_LIGHTMAP, text, colours);
|
drawText(graphics.pose(), graphics.bufferSource(), leftPos + X_TEXT_MARGIN, topPos + Y_TEXT_MARGIN, PrintoutData.LINES_PER_PAGE * page, FULL_BRIGHT_LIGHTMAP, printout.text(), printout.colour());
|
||||||
|
|
||||||
graphics.pose().popPose();
|
graphics.pose().popPose();
|
||||||
}
|
}
|
||||||
@@ -98,4 +126,21 @@ public class PrintoutScreen extends AbstractContainerScreen<HeldItemMenu> {
|
|||||||
protected void renderLabels(GuiGraphics graphics, int mouseX, int mouseY) {
|
protected void renderLabels(GuiGraphics graphics, int mouseX, int mouseY) {
|
||||||
// Skip rendering labels.
|
// Skip rendering labels.
|
||||||
}
|
}
|
||||||
|
|
||||||
|
record PrintoutInfo(int pages, boolean book, TextBuffer[] text, TextBuffer[] colour) {
|
||||||
|
public static final PrintoutInfo DEFAULT = of(PrintoutData.EMPTY, false);
|
||||||
|
|
||||||
|
public static PrintoutInfo of(PrintoutData printout, boolean book) {
|
||||||
|
var text = new TextBuffer[printout.lines().size()];
|
||||||
|
var colours = new TextBuffer[printout.lines().size()];
|
||||||
|
for (var i = 0; i < text.length; i++) {
|
||||||
|
var line = printout.lines().get(i);
|
||||||
|
text[i] = new TextBuffer(line.text());
|
||||||
|
colours[i] = new TextBuffer(line.foreground());
|
||||||
|
}
|
||||||
|
|
||||||
|
var pages = Math.max(text.length / PrintoutData.LINES_PER_PAGE, 1);
|
||||||
|
return new PrintoutInfo(pages, book, text, colours);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@@ -0,0 +1,117 @@
|
|||||||
|
// SPDX-FileCopyrightText: 2024 The CC: Tweaked Developers
|
||||||
|
//
|
||||||
|
// SPDX-License-Identifier: MPL-2.0
|
||||||
|
|
||||||
|
package dan200.computercraft.client.model;
|
||||||
|
|
||||||
|
import com.mojang.blaze3d.vertex.PoseStack;
|
||||||
|
import com.mojang.blaze3d.vertex.VertexConsumer;
|
||||||
|
import dan200.computercraft.api.ComputerCraftAPI;
|
||||||
|
import dan200.computercraft.client.render.CustomLecternRenderer;
|
||||||
|
import dan200.computercraft.shared.media.items.PrintoutItem;
|
||||||
|
import net.minecraft.client.model.geom.ModelPart;
|
||||||
|
import net.minecraft.client.model.geom.PartPose;
|
||||||
|
import net.minecraft.client.model.geom.builders.CubeListBuilder;
|
||||||
|
import net.minecraft.client.model.geom.builders.MeshDefinition;
|
||||||
|
import net.minecraft.client.resources.model.Material;
|
||||||
|
import net.minecraft.resources.ResourceLocation;
|
||||||
|
import net.minecraft.world.inventory.InventoryMenu;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A model for {@linkplain PrintoutItem printouts} placed on a lectern.
|
||||||
|
* <p>
|
||||||
|
* This provides two models, {@linkplain #renderPages(PoseStack, VertexConsumer, int, int, int) one for a variable
|
||||||
|
* number of pages}, and {@linkplain #renderBook(PoseStack, VertexConsumer, int, int) one for books}.
|
||||||
|
*
|
||||||
|
* @see CustomLecternRenderer
|
||||||
|
*/
|
||||||
|
public class LecternPrintoutModel {
|
||||||
|
public static final ResourceLocation TEXTURE = ResourceLocation.fromNamespaceAndPath(ComputerCraftAPI.MOD_ID, "entity/printout");
|
||||||
|
public static final Material MATERIAL = new Material(InventoryMenu.BLOCK_ATLAS, TEXTURE);
|
||||||
|
|
||||||
|
private static final int TEXTURE_WIDTH = 32;
|
||||||
|
private static final int TEXTURE_HEIGHT = 32;
|
||||||
|
|
||||||
|
private static final String PAGE_1 = "page_1";
|
||||||
|
private static final String PAGE_2 = "page_2";
|
||||||
|
private static final String PAGE_3 = "page_3";
|
||||||
|
private static final List<String> PAGES = List.of(PAGE_1, PAGE_2, PAGE_3);
|
||||||
|
|
||||||
|
private final ModelPart pagesRoot;
|
||||||
|
private final ModelPart bookRoot;
|
||||||
|
private final ModelPart[] pages;
|
||||||
|
|
||||||
|
public LecternPrintoutModel() {
|
||||||
|
pagesRoot = buildPages();
|
||||||
|
bookRoot = buildBook();
|
||||||
|
pages = PAGES.stream().map(pagesRoot::getChild).toArray(ModelPart[]::new);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static ModelPart buildPages() {
|
||||||
|
var mesh = new MeshDefinition();
|
||||||
|
var parts = mesh.getRoot();
|
||||||
|
parts.addOrReplaceChild(
|
||||||
|
PAGE_1,
|
||||||
|
CubeListBuilder.create().texOffs(0, 0).addBox(-0.005f, -4.0f, -2.5f, 1f, 8.0f, 5.0f),
|
||||||
|
PartPose.ZERO
|
||||||
|
);
|
||||||
|
|
||||||
|
parts.addOrReplaceChild(
|
||||||
|
PAGE_2,
|
||||||
|
CubeListBuilder.create().texOffs(12, 0).addBox(-0.005f, -4.0f, -2.5f, 1f, 8.0f, 5.0f),
|
||||||
|
PartPose.offsetAndRotation(-0.125f, 0, 1.5f, (float) Math.PI * (1f / 16), 0, 0)
|
||||||
|
);
|
||||||
|
parts.addOrReplaceChild(
|
||||||
|
PAGE_3,
|
||||||
|
CubeListBuilder.create().texOffs(12, 0).addBox(-0.005f, -4.0f, -2.5f, 1f, 8.0f, 5.0f),
|
||||||
|
PartPose.offsetAndRotation(-0.25f, 0, -1.5f, (float) -Math.PI * (2f / 16), 0, 0)
|
||||||
|
);
|
||||||
|
|
||||||
|
return mesh.getRoot().bake(TEXTURE_WIDTH, TEXTURE_HEIGHT);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static ModelPart buildBook() {
|
||||||
|
var mesh = new MeshDefinition();
|
||||||
|
var parts = mesh.getRoot();
|
||||||
|
|
||||||
|
parts.addOrReplaceChild(
|
||||||
|
"spine",
|
||||||
|
CubeListBuilder.create().texOffs(12, 15).addBox(-0.005f, -5.0f, -0.5f, 0, 10, 1.0f),
|
||||||
|
PartPose.ZERO
|
||||||
|
);
|
||||||
|
|
||||||
|
var angle = (float) Math.toRadians(5);
|
||||||
|
parts.addOrReplaceChild(
|
||||||
|
"left",
|
||||||
|
CubeListBuilder.create()
|
||||||
|
.texOffs(0, 10).addBox(0, -5.0f, -6.0f, 0, 10, 6.0f)
|
||||||
|
.texOffs(0, 0).addBox(0.005f, -4.0f, -5.0f, 1.0f, 8.0f, 5.0f),
|
||||||
|
PartPose.offsetAndRotation(-0.005f, 0, -0.5f, 0, -angle, 0)
|
||||||
|
);
|
||||||
|
|
||||||
|
parts.addOrReplaceChild(
|
||||||
|
"right",
|
||||||
|
CubeListBuilder.create()
|
||||||
|
.texOffs(14, 10).addBox(0, -5.0f, 0, 0, 10, 6.0f)
|
||||||
|
.texOffs(0, 0).addBox(0.005f, -4.0f, 0, 1.0f, 8.0f, 5.0f),
|
||||||
|
PartPose.offsetAndRotation(-0.005f, 0, 0.5f, 0, angle, 0)
|
||||||
|
);
|
||||||
|
|
||||||
|
return mesh.getRoot().bake(TEXTURE_WIDTH, TEXTURE_HEIGHT);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void renderBook(PoseStack poseStack, VertexConsumer buffer, int packedLight, int packedOverlay) {
|
||||||
|
bookRoot.render(poseStack, buffer, packedLight, packedOverlay);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void renderPages(PoseStack poseStack, VertexConsumer buffer, int packedLight, int packedOverlay, int pageCount) {
|
||||||
|
if (pageCount > pages.length) pageCount = pages.length;
|
||||||
|
var i = 0;
|
||||||
|
for (; i < pageCount; i++) pages[i].visible = true;
|
||||||
|
for (; i < pages.length; i++) pages[i].visible = false;
|
||||||
|
|
||||||
|
pagesRoot.render(poseStack, buffer, packedLight, packedOverlay);
|
||||||
|
}
|
||||||
|
}
|
@@ -0,0 +1,52 @@
|
|||||||
|
// SPDX-FileCopyrightText: 2024 The CC: Tweaked Developers
|
||||||
|
//
|
||||||
|
// SPDX-License-Identifier: MPL-2.0
|
||||||
|
|
||||||
|
package dan200.computercraft.client.render;
|
||||||
|
|
||||||
|
import com.mojang.blaze3d.vertex.PoseStack;
|
||||||
|
import com.mojang.math.Axis;
|
||||||
|
import dan200.computercraft.client.model.LecternPrintoutModel;
|
||||||
|
import dan200.computercraft.shared.lectern.CustomLecternBlockEntity;
|
||||||
|
import dan200.computercraft.shared.media.items.PrintoutData;
|
||||||
|
import dan200.computercraft.shared.media.items.PrintoutItem;
|
||||||
|
import net.minecraft.client.renderer.MultiBufferSource;
|
||||||
|
import net.minecraft.client.renderer.RenderType;
|
||||||
|
import net.minecraft.client.renderer.blockentity.BlockEntityRenderer;
|
||||||
|
import net.minecraft.client.renderer.blockentity.BlockEntityRendererProvider;
|
||||||
|
import net.minecraft.client.renderer.blockentity.LecternRenderer;
|
||||||
|
import net.minecraft.world.level.block.LecternBlock;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A block entity renderer for our {@linkplain CustomLecternBlockEntity lectern}.
|
||||||
|
* <p>
|
||||||
|
* This largely follows {@link LecternRenderer}, but with support for multiple types of item.
|
||||||
|
*/
|
||||||
|
public class CustomLecternRenderer implements BlockEntityRenderer<CustomLecternBlockEntity> {
|
||||||
|
private final LecternPrintoutModel printoutModel;
|
||||||
|
|
||||||
|
public CustomLecternRenderer(BlockEntityRendererProvider.Context context) {
|
||||||
|
printoutModel = new LecternPrintoutModel();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void render(CustomLecternBlockEntity lectern, float partialTick, PoseStack poseStack, MultiBufferSource buffer, int packedLight, int packedOverlay) {
|
||||||
|
poseStack.pushPose();
|
||||||
|
poseStack.translate(0.5f, 1.0625f, 0.5f);
|
||||||
|
poseStack.mulPose(Axis.YP.rotationDegrees(-lectern.getBlockState().getValue(LecternBlock.FACING).getClockWise().toYRot()));
|
||||||
|
poseStack.mulPose(Axis.ZP.rotationDegrees(67.5f));
|
||||||
|
poseStack.translate(0, -0.125f, 0);
|
||||||
|
|
||||||
|
var item = lectern.getItem();
|
||||||
|
if (item.getItem() instanceof PrintoutItem printout) {
|
||||||
|
var vertexConsumer = LecternPrintoutModel.MATERIAL.buffer(buffer, RenderType::entitySolid);
|
||||||
|
if (printout.getType() == PrintoutItem.Type.BOOK) {
|
||||||
|
printoutModel.renderBook(poseStack, vertexConsumer, packedLight, packedOverlay);
|
||||||
|
} else {
|
||||||
|
printoutModel.renderPages(poseStack, vertexConsumer, packedLight, packedOverlay, PrintoutData.getOrEmpty(item).pages());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
poseStack.popPose();
|
||||||
|
}
|
||||||
|
}
|
@@ -5,6 +5,7 @@
|
|||||||
package dan200.computercraft.data.client;
|
package dan200.computercraft.data.client;
|
||||||
|
|
||||||
import dan200.computercraft.client.gui.GuiSprites;
|
import dan200.computercraft.client.gui.GuiSprites;
|
||||||
|
import dan200.computercraft.client.model.LecternPrintoutModel;
|
||||||
import dan200.computercraft.data.DataProviders;
|
import dan200.computercraft.data.DataProviders;
|
||||||
import dan200.computercraft.shared.turtle.TurtleOverlay;
|
import dan200.computercraft.shared.turtle.TurtleOverlay;
|
||||||
import dan200.computercraft.shared.turtle.inventory.UpgradeSlot;
|
import dan200.computercraft.shared.turtle.inventory.UpgradeSlot;
|
||||||
@@ -33,7 +34,8 @@ public final class ClientDataProviders {
|
|||||||
generator.addFromCodec("Block atlases", PackType.CLIENT_RESOURCES, "atlases", SpriteSources.FILE_CODEC, out -> {
|
generator.addFromCodec("Block atlases", PackType.CLIENT_RESOURCES, "atlases", SpriteSources.FILE_CODEC, out -> {
|
||||||
out.accept(ResourceLocation.withDefaultNamespace("blocks"), List.of(
|
out.accept(ResourceLocation.withDefaultNamespace("blocks"), List.of(
|
||||||
new SingleFile(UpgradeSlot.LEFT_UPGRADE, Optional.empty()),
|
new SingleFile(UpgradeSlot.LEFT_UPGRADE, Optional.empty()),
|
||||||
new SingleFile(UpgradeSlot.RIGHT_UPGRADE, Optional.empty())
|
new SingleFile(UpgradeSlot.RIGHT_UPGRADE, Optional.empty()),
|
||||||
|
new SingleFile(LecternPrintoutModel.TEXTURE, Optional.empty())
|
||||||
));
|
));
|
||||||
out.accept(GuiSprites.SPRITE_SHEET, Stream.of(
|
out.accept(GuiSprites.SPRITE_SHEET, Stream.of(
|
||||||
// Buttons
|
// Buttons
|
||||||
|
8
projects/common/src/generated/resources/assets/computercraft/blockstates/lectern.json
generated
Normal file
8
projects/common/src/generated/resources/assets/computercraft/blockstates/lectern.json
generated
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
{
|
||||||
|
"variants": {
|
||||||
|
"facing=east": {"model": "minecraft:block/lectern", "y": 90},
|
||||||
|
"facing=north": {"model": "minecraft:block/lectern", "y": 0},
|
||||||
|
"facing=south": {"model": "minecraft:block/lectern", "y": 180},
|
||||||
|
"facing=west": {"model": "minecraft:block/lectern", "y": 270}
|
||||||
|
}
|
||||||
|
}
|
@@ -1,6 +1,7 @@
|
|||||||
{
|
{
|
||||||
"sources": [
|
"sources": [
|
||||||
{"type": "minecraft:single", "resource": "computercraft:gui/turtle_upgrade_left"},
|
{"type": "minecraft:single", "resource": "computercraft:gui/turtle_upgrade_left"},
|
||||||
{"type": "minecraft:single", "resource": "computercraft:gui/turtle_upgrade_right"}
|
{"type": "minecraft:single", "resource": "computercraft:gui/turtle_upgrade_right"},
|
||||||
|
{"type": "minecraft:single", "resource": "computercraft:entity/printout"}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
12
projects/common/src/generated/resources/data/computercraft/loot_table/blocks/lectern.json
generated
Normal file
12
projects/common/src/generated/resources/data/computercraft/loot_table/blocks/lectern.json
generated
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
{
|
||||||
|
"type": "minecraft:block",
|
||||||
|
"pools": [
|
||||||
|
{
|
||||||
|
"bonus_rolls": 0.0,
|
||||||
|
"conditions": [{"condition": "minecraft:survives_explosion"}],
|
||||||
|
"entries": [{"type": "minecraft:item", "name": "minecraft:lectern"}],
|
||||||
|
"rolls": 1.0
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"random_sequence": "computercraft:blocks/lectern"
|
||||||
|
}
|
@@ -23,6 +23,7 @@ import net.minecraft.data.models.BlockModelGenerators;
|
|||||||
import net.minecraft.data.models.blockstates.*;
|
import net.minecraft.data.models.blockstates.*;
|
||||||
import net.minecraft.data.models.model.*;
|
import net.minecraft.data.models.model.*;
|
||||||
import net.minecraft.resources.ResourceLocation;
|
import net.minecraft.resources.ResourceLocation;
|
||||||
|
import net.minecraft.world.level.block.Blocks;
|
||||||
import net.minecraft.world.level.block.state.properties.BlockStateProperties;
|
import net.minecraft.world.level.block.state.properties.BlockStateProperties;
|
||||||
import net.minecraft.world.level.block.state.properties.BooleanProperty;
|
import net.minecraft.world.level.block.state.properties.BooleanProperty;
|
||||||
import net.minecraft.world.level.block.state.properties.Property;
|
import net.minecraft.world.level.block.state.properties.Property;
|
||||||
@@ -100,6 +101,11 @@ class BlockModelProvider {
|
|||||||
registerTurtleUpgrade(generators, "block/turtle_speaker", "block/turtle_speaker_face");
|
registerTurtleUpgrade(generators, "block/turtle_speaker", "block/turtle_speaker_face");
|
||||||
registerTurtleModem(generators, "block/turtle_modem_normal", "block/wireless_modem_normal_face");
|
registerTurtleModem(generators, "block/turtle_modem_normal", "block/wireless_modem_normal_face");
|
||||||
registerTurtleModem(generators, "block/turtle_modem_advanced", "block/wireless_modem_advanced_face");
|
registerTurtleModem(generators, "block/turtle_modem_advanced", "block/wireless_modem_advanced_face");
|
||||||
|
|
||||||
|
generators.blockStateOutput.accept(MultiVariantGenerator.multiVariant(
|
||||||
|
ModRegistry.Blocks.LECTERN.get(),
|
||||||
|
Variant.variant().with(VariantProperties.MODEL, ModelLocationUtils.getModelLocation(Blocks.LECTERN))
|
||||||
|
).with(createHorizontalFacingDispatch()));
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void registerDiskDrive(BlockModelGenerators generators) {
|
private static void registerDiskDrive(BlockModelGenerators generators) {
|
||||||
|
@@ -288,7 +288,9 @@ public final class LanguageProvider implements DataProvider {
|
|||||||
return Stream.of(
|
return Stream.of(
|
||||||
BuiltInRegistries.BLOCK.holders()
|
BuiltInRegistries.BLOCK.holders()
|
||||||
.filter(x -> x.key().location().getNamespace().equals(ComputerCraftAPI.MOD_ID))
|
.filter(x -> x.key().location().getNamespace().equals(ComputerCraftAPI.MOD_ID))
|
||||||
.map(x -> x.value().getDescriptionId()),
|
.map(x -> x.value().getDescriptionId())
|
||||||
|
// Exclude blocks that just reuse vanilla translations, such as the lectern.
|
||||||
|
.filter(x -> !x.startsWith("block.minecraft.")),
|
||||||
BuiltInRegistries.ITEM.holders()
|
BuiltInRegistries.ITEM.holders()
|
||||||
.filter(x -> x.key().location().getNamespace().equals(ComputerCraftAPI.MOD_ID))
|
.filter(x -> x.key().location().getNamespace().equals(ComputerCraftAPI.MOD_ID))
|
||||||
.map(x -> x.value().getDescriptionId()),
|
.map(x -> x.value().getDescriptionId()),
|
||||||
|
@@ -14,6 +14,7 @@ import dan200.computercraft.shared.peripheral.modem.wired.CableModemVariant;
|
|||||||
import net.minecraft.advancements.critereon.StatePropertiesPredicate;
|
import net.minecraft.advancements.critereon.StatePropertiesPredicate;
|
||||||
import net.minecraft.data.loot.LootTableProvider.SubProviderEntry;
|
import net.minecraft.data.loot.LootTableProvider.SubProviderEntry;
|
||||||
import net.minecraft.resources.ResourceKey;
|
import net.minecraft.resources.ResourceKey;
|
||||||
|
import net.minecraft.world.item.Items;
|
||||||
import net.minecraft.world.level.block.Block;
|
import net.minecraft.world.level.block.Block;
|
||||||
import net.minecraft.world.level.storage.loot.LootPool;
|
import net.minecraft.world.level.storage.loot.LootPool;
|
||||||
import net.minecraft.world.level.storage.loot.LootTable;
|
import net.minecraft.world.level.storage.loot.LootTable;
|
||||||
@@ -56,6 +57,8 @@ class LootTableProvider {
|
|||||||
computerDrop(add, ModRegistry.Blocks.TURTLE_NORMAL);
|
computerDrop(add, ModRegistry.Blocks.TURTLE_NORMAL);
|
||||||
computerDrop(add, ModRegistry.Blocks.TURTLE_ADVANCED);
|
computerDrop(add, ModRegistry.Blocks.TURTLE_ADVANCED);
|
||||||
|
|
||||||
|
blockDrop(add, ModRegistry.Blocks.LECTERN, LootItem.lootTableItem(Items.LECTERN), ExplosionCondition.survivesExplosion());
|
||||||
|
|
||||||
add.accept(ModRegistry.Blocks.CABLE.get().getLootTable(), LootTable
|
add.accept(ModRegistry.Blocks.CABLE.get().getLootTable(), LootTable
|
||||||
.lootTable()
|
.lootTable()
|
||||||
.withPool(LootPool.lootPool()
|
.withPool(LootPool.lootPool()
|
||||||
|
@@ -5,9 +5,9 @@
|
|||||||
package dan200.computercraft.data;
|
package dan200.computercraft.data;
|
||||||
|
|
||||||
import dan200.computercraft.api.ComputerCraftTags;
|
import dan200.computercraft.api.ComputerCraftTags;
|
||||||
import dan200.computercraft.shared.util.RegistryHelper;
|
|
||||||
import dan200.computercraft.shared.ModRegistry;
|
import dan200.computercraft.shared.ModRegistry;
|
||||||
import dan200.computercraft.shared.integration.ExternalModTags;
|
import dan200.computercraft.shared.integration.ExternalModTags;
|
||||||
|
import dan200.computercraft.shared.util.RegistryHelper;
|
||||||
import net.minecraft.core.Registry;
|
import net.minecraft.core.Registry;
|
||||||
import net.minecraft.data.tags.ItemTagsProvider;
|
import net.minecraft.data.tags.ItemTagsProvider;
|
||||||
import net.minecraft.data.tags.TagsProvider;
|
import net.minecraft.data.tags.TagsProvider;
|
||||||
@@ -107,6 +107,7 @@ class TagProvider {
|
|||||||
ModRegistry.Items.MONITOR_ADVANCED.get()
|
ModRegistry.Items.MONITOR_ADVANCED.get()
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// Allow printed books to be placed in bookshelves.
|
||||||
tags.tag(ItemTags.BOOKSHELF_BOOKS).add(ModRegistry.Items.PRINTED_BOOK.get());
|
tags.tag(ItemTags.BOOKSHELF_BOOKS).add(ModRegistry.Items.PRINTED_BOOK.get());
|
||||||
|
|
||||||
tags.tag(ComputerCraftTags.Items.TURTLE_CAN_PLACE)
|
tags.tag(ComputerCraftTags.Items.TURTLE_CAN_PLACE)
|
||||||
|
@@ -15,7 +15,7 @@ import java.util.*;
|
|||||||
* @param <T> The type of object that this registry provides details for.
|
* @param <T> The type of object that this registry provides details for.
|
||||||
*/
|
*/
|
||||||
public class DetailRegistryImpl<T> implements DetailRegistry<T> {
|
public class DetailRegistryImpl<T> implements DetailRegistry<T> {
|
||||||
private final Collection<DetailProvider<T>> providers = new ArrayList<>();
|
private final Collection<DetailProvider<? super T>> providers = new ArrayList<>();
|
||||||
private final DetailProvider<T> basic;
|
private final DetailProvider<T> basic;
|
||||||
|
|
||||||
public DetailRegistryImpl(DetailProvider<T> basic) {
|
public DetailRegistryImpl(DetailProvider<T> basic) {
|
||||||
@@ -24,7 +24,7 @@ public class DetailRegistryImpl<T> implements DetailRegistry<T> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public synchronized void addProvider(DetailProvider<T> provider) {
|
public synchronized void addProvider(DetailProvider<? super T> provider) {
|
||||||
Objects.requireNonNull(provider, "provider cannot be null");
|
Objects.requireNonNull(provider, "provider cannot be null");
|
||||||
if (!providers.contains(provider)) providers.add(provider);
|
if (!providers.contains(provider)) providers.add(provider);
|
||||||
}
|
}
|
||||||
|
@@ -9,6 +9,7 @@ import dan200.computercraft.core.apis.http.NetworkUtils;
|
|||||||
import dan200.computercraft.shared.computer.core.ResourceMount;
|
import dan200.computercraft.shared.computer.core.ResourceMount;
|
||||||
import dan200.computercraft.shared.computer.core.ServerContext;
|
import dan200.computercraft.shared.computer.core.ServerContext;
|
||||||
import dan200.computercraft.shared.computer.metrics.ComputerMBean;
|
import dan200.computercraft.shared.computer.metrics.ComputerMBean;
|
||||||
|
import dan200.computercraft.shared.lectern.CustomLecternBlock;
|
||||||
import dan200.computercraft.shared.peripheral.monitor.MonitorWatcher;
|
import dan200.computercraft.shared.peripheral.monitor.MonitorWatcher;
|
||||||
import dan200.computercraft.shared.util.DropConsumer;
|
import dan200.computercraft.shared.util.DropConsumer;
|
||||||
import dan200.computercraft.shared.util.TickScheduler;
|
import dan200.computercraft.shared.util.TickScheduler;
|
||||||
@@ -20,16 +21,23 @@ import net.minecraft.server.dedicated.DedicatedServer;
|
|||||||
import net.minecraft.server.level.ServerLevel;
|
import net.minecraft.server.level.ServerLevel;
|
||||||
import net.minecraft.server.level.ServerPlayer;
|
import net.minecraft.server.level.ServerPlayer;
|
||||||
import net.minecraft.server.packs.resources.PreparableReloadListener;
|
import net.minecraft.server.packs.resources.PreparableReloadListener;
|
||||||
|
import net.minecraft.world.InteractionHand;
|
||||||
|
import net.minecraft.world.InteractionResult;
|
||||||
import net.minecraft.world.entity.Entity;
|
import net.minecraft.world.entity.Entity;
|
||||||
|
import net.minecraft.world.entity.player.Player;
|
||||||
import net.minecraft.world.item.CreativeModeTab;
|
import net.minecraft.world.item.CreativeModeTab;
|
||||||
import net.minecraft.world.item.CreativeModeTabs;
|
import net.minecraft.world.item.CreativeModeTabs;
|
||||||
import net.minecraft.world.item.ItemStack;
|
import net.minecraft.world.item.ItemStack;
|
||||||
|
import net.minecraft.world.level.Level;
|
||||||
|
import net.minecraft.world.level.block.Blocks;
|
||||||
|
import net.minecraft.world.level.block.LecternBlock;
|
||||||
import net.minecraft.world.level.chunk.LevelChunk;
|
import net.minecraft.world.level.chunk.LevelChunk;
|
||||||
import net.minecraft.world.level.storage.loot.BuiltInLootTables;
|
import net.minecraft.world.level.storage.loot.BuiltInLootTables;
|
||||||
import net.minecraft.world.level.storage.loot.LootPool;
|
import net.minecraft.world.level.storage.loot.LootPool;
|
||||||
import net.minecraft.world.level.storage.loot.LootTable;
|
import net.minecraft.world.level.storage.loot.LootTable;
|
||||||
import net.minecraft.world.level.storage.loot.entries.NestedLootTable;
|
import net.minecraft.world.level.storage.loot.entries.NestedLootTable;
|
||||||
import net.minecraft.world.level.storage.loot.providers.number.ConstantValue;
|
import net.minecraft.world.level.storage.loot.providers.number.ConstantValue;
|
||||||
|
import net.minecraft.world.phys.BlockHitResult;
|
||||||
|
|
||||||
import javax.annotation.Nullable;
|
import javax.annotation.Nullable;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
@@ -92,6 +100,20 @@ public final class CommonHooks {
|
|||||||
TickScheduler.onChunkTicketChanged(level, chunkPos, oldLevel, newLevel);
|
TickScheduler.onChunkTicketChanged(level, chunkPos, oldLevel, newLevel);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static InteractionResult onUseBlock(Player player, Level level, InteractionHand hand, BlockHitResult hitResult) {
|
||||||
|
if (player.isSpectator()) return InteractionResult.PASS;
|
||||||
|
|
||||||
|
var pos = hitResult.getBlockPos();
|
||||||
|
var heldItem = player.getItemInHand(hand);
|
||||||
|
var blockState = level.getBlockState(pos);
|
||||||
|
|
||||||
|
if (blockState.is(Blocks.LECTERN) && !blockState.getValue(LecternBlock.HAS_BOOK)) {
|
||||||
|
return CustomLecternBlock.tryPlaceItem(player, level, pos, blockState, heldItem);
|
||||||
|
}
|
||||||
|
|
||||||
|
return InteractionResult.PASS;
|
||||||
|
}
|
||||||
|
|
||||||
public static final ResourceKey<LootTable> TREASURE_DISK_LOOT = ResourceKey.create(Registries.LOOT_TABLE, ResourceLocation.fromNamespaceAndPath(ComputerCraftAPI.MOD_ID, "treasure_disk"));
|
public static final ResourceKey<LootTable> TREASURE_DISK_LOOT = ResourceKey.create(Registries.LOOT_TABLE, ResourceLocation.fromNamespaceAndPath(ComputerCraftAPI.MOD_ID, "treasure_disk"));
|
||||||
|
|
||||||
private static final Set<ResourceKey<LootTable>> TREASURE_DISK_LOOT_TABLES = Set.of(
|
private static final Set<ResourceKey<LootTable>> TREASURE_DISK_LOOT_TABLES = Set.of(
|
||||||
|
@@ -26,7 +26,6 @@ import dan200.computercraft.shared.command.arguments.TrackingFieldArgumentType;
|
|||||||
import dan200.computercraft.shared.common.ClearColourRecipe;
|
import dan200.computercraft.shared.common.ClearColourRecipe;
|
||||||
import dan200.computercraft.shared.common.ColourableRecipe;
|
import dan200.computercraft.shared.common.ColourableRecipe;
|
||||||
import dan200.computercraft.shared.common.DefaultBundledRedstoneProvider;
|
import dan200.computercraft.shared.common.DefaultBundledRedstoneProvider;
|
||||||
import dan200.computercraft.shared.common.HeldItemMenu;
|
|
||||||
import dan200.computercraft.shared.computer.apis.CommandAPI;
|
import dan200.computercraft.shared.computer.apis.CommandAPI;
|
||||||
import dan200.computercraft.shared.computer.blocks.CommandComputerBlock;
|
import dan200.computercraft.shared.computer.blocks.CommandComputerBlock;
|
||||||
import dan200.computercraft.shared.computer.blocks.ComputerBlock;
|
import dan200.computercraft.shared.computer.blocks.ComputerBlock;
|
||||||
@@ -44,12 +43,14 @@ import dan200.computercraft.shared.data.PlayerCreativeLootCondition;
|
|||||||
import dan200.computercraft.shared.details.BlockDetails;
|
import dan200.computercraft.shared.details.BlockDetails;
|
||||||
import dan200.computercraft.shared.details.ItemDetails;
|
import dan200.computercraft.shared.details.ItemDetails;
|
||||||
import dan200.computercraft.shared.integration.PermissionRegistry;
|
import dan200.computercraft.shared.integration.PermissionRegistry;
|
||||||
|
import dan200.computercraft.shared.lectern.CustomLecternBlock;
|
||||||
|
import dan200.computercraft.shared.lectern.CustomLecternBlockEntity;
|
||||||
|
import dan200.computercraft.shared.media.PrintoutMenu;
|
||||||
import dan200.computercraft.shared.media.items.*;
|
import dan200.computercraft.shared.media.items.*;
|
||||||
import dan200.computercraft.shared.media.recipes.DiskRecipe;
|
import dan200.computercraft.shared.media.recipes.DiskRecipe;
|
||||||
import dan200.computercraft.shared.media.recipes.PrintoutRecipe;
|
import dan200.computercraft.shared.media.recipes.PrintoutRecipe;
|
||||||
import dan200.computercraft.shared.network.container.ComputerContainerData;
|
import dan200.computercraft.shared.network.container.ComputerContainerData;
|
||||||
import dan200.computercraft.shared.network.container.ContainerData;
|
import dan200.computercraft.shared.network.container.ContainerData;
|
||||||
import dan200.computercraft.shared.network.container.HeldItemContainerData;
|
|
||||||
import dan200.computercraft.shared.peripheral.diskdrive.DiskDriveBlock;
|
import dan200.computercraft.shared.peripheral.diskdrive.DiskDriveBlock;
|
||||||
import dan200.computercraft.shared.peripheral.diskdrive.DiskDriveBlockEntity;
|
import dan200.computercraft.shared.peripheral.diskdrive.DiskDriveBlockEntity;
|
||||||
import dan200.computercraft.shared.peripheral.diskdrive.DiskDriveMenu;
|
import dan200.computercraft.shared.peripheral.diskdrive.DiskDriveMenu;
|
||||||
@@ -114,9 +115,11 @@ import net.minecraft.world.item.crafting.Recipe;
|
|||||||
import net.minecraft.world.item.crafting.RecipeSerializer;
|
import net.minecraft.world.item.crafting.RecipeSerializer;
|
||||||
import net.minecraft.world.item.crafting.SimpleCraftingRecipeSerializer;
|
import net.minecraft.world.item.crafting.SimpleCraftingRecipeSerializer;
|
||||||
import net.minecraft.world.level.block.Block;
|
import net.minecraft.world.level.block.Block;
|
||||||
|
import net.minecraft.world.level.block.SoundType;
|
||||||
import net.minecraft.world.level.block.entity.BlockEntity;
|
import net.minecraft.world.level.block.entity.BlockEntity;
|
||||||
import net.minecraft.world.level.block.entity.BlockEntityType;
|
import net.minecraft.world.level.block.entity.BlockEntityType;
|
||||||
import net.minecraft.world.level.block.state.BlockBehaviour;
|
import net.minecraft.world.level.block.state.BlockBehaviour;
|
||||||
|
import net.minecraft.world.level.block.state.properties.NoteBlockInstrument;
|
||||||
import net.minecraft.world.level.material.MapColor;
|
import net.minecraft.world.level.material.MapColor;
|
||||||
import net.minecraft.world.level.storage.loot.predicates.LootItemConditionType;
|
import net.minecraft.world.level.storage.loot.predicates.LootItemConditionType;
|
||||||
|
|
||||||
@@ -185,6 +188,10 @@ public final class ModRegistry {
|
|||||||
public static final RegistryEntry<WiredModemFullBlock> WIRED_MODEM_FULL = REGISTRY.register("wired_modem_full",
|
public static final RegistryEntry<WiredModemFullBlock> WIRED_MODEM_FULL = REGISTRY.register("wired_modem_full",
|
||||||
() -> new WiredModemFullBlock(modemProperties().mapColor(MapColor.STONE)));
|
() -> new WiredModemFullBlock(modemProperties().mapColor(MapColor.STONE)));
|
||||||
public static final RegistryEntry<CableBlock> CABLE = REGISTRY.register("cable", () -> new CableBlock(modemProperties().mapColor(MapColor.STONE)));
|
public static final RegistryEntry<CableBlock> CABLE = REGISTRY.register("cable", () -> new CableBlock(modemProperties().mapColor(MapColor.STONE)));
|
||||||
|
|
||||||
|
public static final RegistryEntry<CustomLecternBlock> LECTERN = REGISTRY.register("lectern", () -> new CustomLecternBlock(
|
||||||
|
BlockBehaviour.Properties.of().mapColor(MapColor.WOOD).instrument(NoteBlockInstrument.BASS).strength(2.5F).sound(SoundType.WOOD).ignitedByLava()
|
||||||
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
public static class BlockEntities {
|
public static class BlockEntities {
|
||||||
@@ -226,6 +233,8 @@ public final class ModRegistry {
|
|||||||
ofBlock(Blocks.WIRELESS_MODEM_NORMAL, (p, s) -> new WirelessModemBlockEntity(BlockEntities.WIRELESS_MODEM_NORMAL.get(), p, s, false));
|
ofBlock(Blocks.WIRELESS_MODEM_NORMAL, (p, s) -> new WirelessModemBlockEntity(BlockEntities.WIRELESS_MODEM_NORMAL.get(), p, s, false));
|
||||||
public static final RegistryEntry<BlockEntityType<WirelessModemBlockEntity>> WIRELESS_MODEM_ADVANCED =
|
public static final RegistryEntry<BlockEntityType<WirelessModemBlockEntity>> WIRELESS_MODEM_ADVANCED =
|
||||||
ofBlock(Blocks.WIRELESS_MODEM_ADVANCED, (p, s) -> new WirelessModemBlockEntity(BlockEntities.WIRELESS_MODEM_ADVANCED.get(), p, s, true));
|
ofBlock(Blocks.WIRELESS_MODEM_ADVANCED, (p, s) -> new WirelessModemBlockEntity(BlockEntities.WIRELESS_MODEM_ADVANCED.get(), p, s, true));
|
||||||
|
|
||||||
|
public static final RegistryEntry<BlockEntityType<CustomLecternBlockEntity>> LECTERN = ofBlock(Blocks.LECTERN, CustomLecternBlockEntity::new);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static final class Items {
|
public static final class Items {
|
||||||
@@ -429,11 +438,8 @@ public final class ModRegistry {
|
|||||||
public static final RegistryEntry<MenuType<PrinterMenu>> PRINTER = REGISTRY.register("printer",
|
public static final RegistryEntry<MenuType<PrinterMenu>> PRINTER = REGISTRY.register("printer",
|
||||||
() -> new MenuType<>(PrinterMenu::new, FeatureFlags.VANILLA_SET));
|
() -> new MenuType<>(PrinterMenu::new, FeatureFlags.VANILLA_SET));
|
||||||
|
|
||||||
public static final RegistryEntry<MenuType<HeldItemMenu>> PRINTOUT = REGISTRY.register("printout",
|
public static final RegistryEntry<MenuType<PrintoutMenu>> PRINTOUT = REGISTRY.register("printout",
|
||||||
() -> ContainerData.toType(
|
() -> new MenuType<>((i, c) -> PrintoutMenu.createRemote(i), FeatureFlags.VANILLA_SET));
|
||||||
HeldItemContainerData.STREAM_CODEC,
|
|
||||||
(id, inventory, data) -> new HeldItemMenu(Menus.PRINTOUT.get(), id, inventory.player, data.hand())
|
|
||||||
));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static class ArgumentTypes {
|
static class ArgumentTypes {
|
||||||
|
@@ -63,7 +63,7 @@ public class TableBuilder {
|
|||||||
/**
|
/**
|
||||||
* Get the number of columns for this table.
|
* Get the number of columns for this table.
|
||||||
* <p>
|
* <p>
|
||||||
* This will be the same as {@link #getHeaders()}'s length if it is is non-{@code null},
|
* This will be the same as {@link #getHeaders()}'s length if it is non-{@code null},
|
||||||
* otherwise the length of the first column.
|
* otherwise the length of the first column.
|
||||||
*
|
*
|
||||||
* @return The number of columns.
|
* @return The number of columns.
|
||||||
|
@@ -1,68 +0,0 @@
|
|||||||
// Copyright Daniel Ratcliffe, 2011-2022. Do not distribute without permission.
|
|
||||||
//
|
|
||||||
// SPDX-License-Identifier: LicenseRef-CCPL
|
|
||||||
|
|
||||||
package dan200.computercraft.shared.common;
|
|
||||||
|
|
||||||
import net.minecraft.network.chat.Component;
|
|
||||||
import net.minecraft.world.InteractionHand;
|
|
||||||
import net.minecraft.world.MenuProvider;
|
|
||||||
import net.minecraft.world.entity.player.Inventory;
|
|
||||||
import net.minecraft.world.entity.player.Player;
|
|
||||||
import net.minecraft.world.inventory.AbstractContainerMenu;
|
|
||||||
import net.minecraft.world.inventory.MenuType;
|
|
||||||
import net.minecraft.world.item.ItemStack;
|
|
||||||
|
|
||||||
import javax.annotation.Nullable;
|
|
||||||
|
|
||||||
public class HeldItemMenu extends AbstractContainerMenu {
|
|
||||||
private final ItemStack stack;
|
|
||||||
private final InteractionHand hand;
|
|
||||||
|
|
||||||
public HeldItemMenu(MenuType<? extends HeldItemMenu> type, int id, Player player, InteractionHand hand) {
|
|
||||||
super(type, id);
|
|
||||||
|
|
||||||
this.hand = hand;
|
|
||||||
stack = player.getItemInHand(hand).copy();
|
|
||||||
}
|
|
||||||
|
|
||||||
public ItemStack getStack() {
|
|
||||||
return stack;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public ItemStack quickMoveStack(Player player, int slot) {
|
|
||||||
return ItemStack.EMPTY;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean stillValid(Player player) {
|
|
||||||
if (!player.isAlive()) return false;
|
|
||||||
|
|
||||||
var stack = player.getItemInHand(hand);
|
|
||||||
return stack == this.stack || !stack.isEmpty() && !this.stack.isEmpty() && stack.getItem() == this.stack.getItem();
|
|
||||||
}
|
|
||||||
|
|
||||||
public static class Factory implements MenuProvider {
|
|
||||||
private final MenuType<HeldItemMenu> type;
|
|
||||||
private final Component name;
|
|
||||||
private final InteractionHand hand;
|
|
||||||
|
|
||||||
public Factory(MenuType<HeldItemMenu> type, ItemStack stack, InteractionHand hand) {
|
|
||||||
this.type = type;
|
|
||||||
name = stack.getHoverName();
|
|
||||||
this.hand = hand;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Component getDisplayName() {
|
|
||||||
return name;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Nullable
|
|
||||||
@Override
|
|
||||||
public AbstractContainerMenu createMenu(int id, Inventory inventory, Player player) {
|
|
||||||
return new HeldItemMenu(type, id, player, hand);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@@ -111,7 +111,7 @@ public abstract class AbstractComputerBlockEntity extends BlockEntity implements
|
|||||||
fresh = false;
|
fresh = false;
|
||||||
computerID = computer.getID();
|
computerID = computer.getID();
|
||||||
|
|
||||||
// If the on state has changed, mark as as dirty.
|
// If the on state has changed, mark as dirty.
|
||||||
var newOn = computer.isOn();
|
var newOn = computer.isOn();
|
||||||
if (on != newOn) {
|
if (on != newOn) {
|
||||||
on = newOn;
|
on = newOn;
|
||||||
|
@@ -53,7 +53,9 @@ public class ItemDetails {
|
|||||||
data.put("itemGroups", getItemGroups(stack));
|
data.put("itemGroups", getItemGroups(stack));
|
||||||
|
|
||||||
var lore = stack.get(DataComponents.LORE);
|
var lore = stack.get(DataComponents.LORE);
|
||||||
if (lore != null) data.put("lore", lore.lines().stream().map(Component::getString).toList());
|
if (lore != null && !lore.lines().isEmpty()) {
|
||||||
|
data.put("lore", lore.lines().stream().map(Component::getString).toList());
|
||||||
|
}
|
||||||
|
|
||||||
var enchants = getAllEnchants(stack);
|
var enchants = getAllEnchants(stack);
|
||||||
if (!enchants.isEmpty()) data.put("enchantments", enchants);
|
if (!enchants.isEmpty()) data.put("enchantments", enchants);
|
||||||
|
@@ -0,0 +1,163 @@
|
|||||||
|
// SPDX-FileCopyrightText: 2024 The CC: Tweaked Developers
|
||||||
|
//
|
||||||
|
// SPDX-License-Identifier: MPL-2.0
|
||||||
|
|
||||||
|
package dan200.computercraft.shared.lectern;
|
||||||
|
|
||||||
|
import dan200.computercraft.shared.ModRegistry;
|
||||||
|
import dan200.computercraft.shared.media.items.PrintoutItem;
|
||||||
|
import net.minecraft.core.BlockPos;
|
||||||
|
import net.minecraft.server.level.ServerLevel;
|
||||||
|
import net.minecraft.stats.Stats;
|
||||||
|
import net.minecraft.util.RandomSource;
|
||||||
|
import net.minecraft.world.InteractionResult;
|
||||||
|
import net.minecraft.world.entity.item.ItemEntity;
|
||||||
|
import net.minecraft.world.entity.player.Player;
|
||||||
|
import net.minecraft.world.item.ItemStack;
|
||||||
|
import net.minecraft.world.item.Items;
|
||||||
|
import net.minecraft.world.item.context.UseOnContext;
|
||||||
|
import net.minecraft.world.level.Level;
|
||||||
|
import net.minecraft.world.level.LevelReader;
|
||||||
|
import net.minecraft.world.level.block.Blocks;
|
||||||
|
import net.minecraft.world.level.block.LecternBlock;
|
||||||
|
import net.minecraft.world.level.block.state.BlockState;
|
||||||
|
import net.minecraft.world.phys.BlockHitResult;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Extends {@link LecternBlock} with support for {@linkplain PrintoutItem printouts}.
|
||||||
|
* <p>
|
||||||
|
* Unlike the vanilla lectern, this block is never empty. If the book is removed from the lectern, it converts back to
|
||||||
|
* its vanilla version (see {@link #clearLectern(Level, BlockPos, BlockState)}).
|
||||||
|
*
|
||||||
|
* @see PrintoutItem#useOn(UseOnContext) Placing books into a lectern.
|
||||||
|
*/
|
||||||
|
public class CustomLecternBlock extends LecternBlock {
|
||||||
|
public CustomLecternBlock(Properties properties) {
|
||||||
|
super(properties);
|
||||||
|
registerDefaultState(defaultBlockState().setValue(HAS_BOOK, true));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Attempt to place an item onto an (empty) lectern.
|
||||||
|
*
|
||||||
|
* @param player The player placing the item.
|
||||||
|
* @param level The current level.
|
||||||
|
* @param pos The position of the lectern.
|
||||||
|
* @param blockState The current state of the lectern.
|
||||||
|
* @param item The item to place in the custom lectern.
|
||||||
|
* @return Whether the item was placed or not.
|
||||||
|
*/
|
||||||
|
public static InteractionResult tryPlaceItem(Player player, Level level, BlockPos pos, BlockState blockState, ItemStack item) {
|
||||||
|
if (item.getItem() instanceof PrintoutItem) {
|
||||||
|
if (!level.isClientSide) replaceLectern(player, level, pos, blockState, item);
|
||||||
|
return InteractionResult.sidedSuccess(level.isClientSide);
|
||||||
|
}
|
||||||
|
|
||||||
|
return InteractionResult.PASS;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Replace a vanilla lectern with a custom one.
|
||||||
|
*
|
||||||
|
* @param player The player placing the item.
|
||||||
|
* @param level The current level.
|
||||||
|
* @param pos The position of the lectern.
|
||||||
|
* @param blockState The current state of the lectern.
|
||||||
|
* @param item The item to place in the custom lectern.
|
||||||
|
*/
|
||||||
|
private static void replaceLectern(Player player, Level level, BlockPos pos, BlockState blockState, ItemStack item) {
|
||||||
|
level.setBlockAndUpdate(pos, ModRegistry.Blocks.LECTERN.get().defaultBlockState()
|
||||||
|
.setValue(HAS_BOOK, true)
|
||||||
|
.setValue(FACING, blockState.getValue(FACING))
|
||||||
|
.setValue(POWERED, blockState.getValue(POWERED)));
|
||||||
|
|
||||||
|
if (level.getBlockEntity(pos) instanceof CustomLecternBlockEntity be) {
|
||||||
|
be.setItem(item.consumeAndReturn(1, player));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Remove a custom lectern and replace it with an empty vanilla one.
|
||||||
|
*
|
||||||
|
* @param level The current level.
|
||||||
|
* @param pos The position of the lectern.
|
||||||
|
* @param blockState The current state of the lectern.
|
||||||
|
*/
|
||||||
|
static void clearLectern(Level level, BlockPos pos, BlockState blockState) {
|
||||||
|
level.setBlockAndUpdate(pos, Blocks.LECTERN.defaultBlockState()
|
||||||
|
.setValue(HAS_BOOK, false)
|
||||||
|
.setValue(FACING, blockState.getValue(FACING))
|
||||||
|
.setValue(POWERED, blockState.getValue(POWERED)));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@Deprecated
|
||||||
|
public ItemStack getCloneItemStack(LevelReader level, BlockPos pos, BlockState state) {
|
||||||
|
return new ItemStack(Items.LECTERN);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void tick(BlockState state, ServerLevel level, BlockPos pos, RandomSource random) {
|
||||||
|
// If we've no lectern, remove it.
|
||||||
|
if (level.getBlockEntity(pos) instanceof CustomLecternBlockEntity lectern && lectern.getItem().isEmpty()) {
|
||||||
|
clearLectern(level, pos, state);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
super.tick(state, level, pos, random);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onRemove(BlockState state, Level level, BlockPos pos, BlockState newState, boolean isMoving) {
|
||||||
|
if (state.is(newState.getBlock())) return;
|
||||||
|
|
||||||
|
if (level.getBlockEntity(pos) instanceof CustomLecternBlockEntity lectern) {
|
||||||
|
dropItem(level, pos, state, lectern.getItem().copy());
|
||||||
|
}
|
||||||
|
|
||||||
|
super.onRemove(state, level, pos, newState, isMoving);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void dropItem(Level level, BlockPos pos, BlockState state, ItemStack stack) {
|
||||||
|
if (stack.isEmpty()) return;
|
||||||
|
|
||||||
|
var direction = state.getValue(FACING);
|
||||||
|
var dx = 0.25 * direction.getStepX();
|
||||||
|
var dz = 0.25 * direction.getStepZ();
|
||||||
|
var entity = new ItemEntity(level, pos.getX() + 0.5 + dx, pos.getY() + 1, pos.getZ() + 0.5 + dz, stack);
|
||||||
|
entity.setDefaultPickUpDelay();
|
||||||
|
level.addFreshEntity(entity);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getDescriptionId() {
|
||||||
|
return Blocks.LECTERN.getDescriptionId();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public CustomLecternBlockEntity newBlockEntity(BlockPos pos, BlockState state) {
|
||||||
|
return new CustomLecternBlockEntity(pos, state);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getAnalogOutputSignal(BlockState blockState, Level level, BlockPos pos) {
|
||||||
|
return level.getBlockEntity(pos) instanceof CustomLecternBlockEntity lectern ? lectern.getRedstoneSignal() : 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public InteractionResult useWithoutItem(BlockState state, Level level, BlockPos pos, Player player, BlockHitResult hit) {
|
||||||
|
if (!level.isClientSide && level.getBlockEntity(pos) instanceof CustomLecternBlockEntity lectern) {
|
||||||
|
if (player.isSecondaryUseActive()) {
|
||||||
|
// When shift+clicked with an empty hand, drop the item and replace with the normal lectern.
|
||||||
|
clearLectern(level, pos, state);
|
||||||
|
} else {
|
||||||
|
// Otherwise open the screen.
|
||||||
|
player.openMenu(lectern);
|
||||||
|
}
|
||||||
|
|
||||||
|
player.awardStat(Stats.INTERACT_WITH_LECTERN);
|
||||||
|
}
|
||||||
|
|
||||||
|
return InteractionResult.sidedSuccess(level.isClientSide);
|
||||||
|
}
|
||||||
|
}
|
@@ -0,0 +1,195 @@
|
|||||||
|
// SPDX-FileCopyrightText: 2024 The CC: Tweaked Developers
|
||||||
|
//
|
||||||
|
// SPDX-License-Identifier: MPL-2.0
|
||||||
|
|
||||||
|
package dan200.computercraft.shared.lectern;
|
||||||
|
|
||||||
|
import dan200.computercraft.shared.ModRegistry;
|
||||||
|
import dan200.computercraft.shared.container.BasicContainer;
|
||||||
|
import dan200.computercraft.shared.container.SingleContainerData;
|
||||||
|
import dan200.computercraft.shared.media.PrintoutMenu;
|
||||||
|
import dan200.computercraft.shared.media.items.PrintoutData;
|
||||||
|
import dan200.computercraft.shared.media.items.PrintoutItem;
|
||||||
|
import dan200.computercraft.shared.util.BlockEntityHelpers;
|
||||||
|
import net.minecraft.core.BlockPos;
|
||||||
|
import net.minecraft.core.HolderLookup;
|
||||||
|
import net.minecraft.nbt.CompoundTag;
|
||||||
|
import net.minecraft.nbt.Tag;
|
||||||
|
import net.minecraft.network.chat.Component;
|
||||||
|
import net.minecraft.network.protocol.Packet;
|
||||||
|
import net.minecraft.network.protocol.game.ClientGamePacketListener;
|
||||||
|
import net.minecraft.network.protocol.game.ClientboundBlockEntityDataPacket;
|
||||||
|
import net.minecraft.util.Mth;
|
||||||
|
import net.minecraft.world.Container;
|
||||||
|
import net.minecraft.world.MenuProvider;
|
||||||
|
import net.minecraft.world.entity.player.Inventory;
|
||||||
|
import net.minecraft.world.entity.player.Player;
|
||||||
|
import net.minecraft.world.inventory.AbstractContainerMenu;
|
||||||
|
import net.minecraft.world.inventory.ContainerData;
|
||||||
|
import net.minecraft.world.item.ItemStack;
|
||||||
|
import net.minecraft.world.level.block.LecternBlock;
|
||||||
|
import net.minecraft.world.level.block.entity.BlockEntity;
|
||||||
|
import net.minecraft.world.level.block.entity.LecternBlockEntity;
|
||||||
|
import net.minecraft.world.level.block.state.BlockState;
|
||||||
|
import org.jetbrains.annotations.Nullable;
|
||||||
|
|
||||||
|
import java.util.AbstractList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The block entity for our {@link CustomLecternBlock}.
|
||||||
|
*
|
||||||
|
* @see LecternBlockEntity
|
||||||
|
*/
|
||||||
|
public final class CustomLecternBlockEntity extends BlockEntity implements MenuProvider {
|
||||||
|
private static final String NBT_ITEM = "Item";
|
||||||
|
private static final String NBT_PAGE = "Page";
|
||||||
|
|
||||||
|
private ItemStack item = ItemStack.EMPTY;
|
||||||
|
private int page, pageCount;
|
||||||
|
|
||||||
|
public CustomLecternBlockEntity(BlockPos pos, BlockState blockState) {
|
||||||
|
super(ModRegistry.BlockEntities.LECTERN.get(), pos, blockState);
|
||||||
|
}
|
||||||
|
|
||||||
|
public ItemStack getItem() {
|
||||||
|
return item;
|
||||||
|
}
|
||||||
|
|
||||||
|
void setItem(ItemStack item) {
|
||||||
|
this.item = item;
|
||||||
|
itemChanged();
|
||||||
|
BlockEntityHelpers.updateBlock(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
int getRedstoneSignal() {
|
||||||
|
if (item.getItem() instanceof PrintoutItem) {
|
||||||
|
var progress = pageCount > 1 ? (float) page / (pageCount - 1) : 1F;
|
||||||
|
return Mth.floor(progress * 14f) + 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
return 15;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called after the item has changed. This sets up the state for the new item.
|
||||||
|
*/
|
||||||
|
private void itemChanged() {
|
||||||
|
if (item.getItem() instanceof PrintoutItem) {
|
||||||
|
pageCount = PrintoutData.getOrEmpty(item).pages();
|
||||||
|
page = Mth.clamp(page, 0, pageCount - 1);
|
||||||
|
} else {
|
||||||
|
pageCount = page = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the current page, emitting a redstone pulse if needed.
|
||||||
|
*
|
||||||
|
* @param page The new page.
|
||||||
|
*/
|
||||||
|
private void setPage(int page) {
|
||||||
|
if (this.page == page) return;
|
||||||
|
|
||||||
|
this.page = page;
|
||||||
|
setChanged();
|
||||||
|
if (getLevel() != null) LecternBlock.signalPageChange(getLevel(), getBlockPos(), getBlockState());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void loadAdditional(CompoundTag tag, HolderLookup.Provider registries) {
|
||||||
|
super.loadAdditional(tag, registries);
|
||||||
|
|
||||||
|
item = tag.contains(NBT_ITEM, Tag.TAG_COMPOUND) ? ItemStack.parseOptional(registries, tag.getCompound(NBT_ITEM)) : ItemStack.EMPTY;
|
||||||
|
page = tag.getInt(NBT_PAGE);
|
||||||
|
itemChanged();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void saveAdditional(CompoundTag tag, HolderLookup.Provider registries) {
|
||||||
|
super.saveAdditional(tag, registries);
|
||||||
|
|
||||||
|
if (!item.isEmpty()) tag.put(NBT_ITEM, item.save(registries));
|
||||||
|
if (item.getItem() instanceof PrintoutItem) tag.putInt(NBT_PAGE, page);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Packet<ClientGamePacketListener> getUpdatePacket() {
|
||||||
|
return ClientboundBlockEntityDataPacket.create(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public CompoundTag getUpdateTag(HolderLookup.Provider registries) {
|
||||||
|
var tag = super.getUpdateTag(registries);
|
||||||
|
tag.put(NBT_ITEM, item.save(registries));
|
||||||
|
return tag;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
@Override
|
||||||
|
public AbstractContainerMenu createMenu(int containerId, Inventory playerInventory, Player player) {
|
||||||
|
var item = getItem();
|
||||||
|
if (item.getItem() instanceof PrintoutItem) {
|
||||||
|
return new PrintoutMenu(
|
||||||
|
containerId, new LecternContainer(), 0,
|
||||||
|
p -> Container.stillValidBlockEntity(this, player, Container.DEFAULT_DISTANCE_BUFFER),
|
||||||
|
new PrintoutContainerData()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Component getDisplayName() {
|
||||||
|
return getItem().getDisplayName();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A read-only container storing the lectern's contents.
|
||||||
|
*/
|
||||||
|
private final class LecternContainer implements BasicContainer {
|
||||||
|
private final List<ItemStack> itemView = new AbstractList<>() {
|
||||||
|
@Override
|
||||||
|
public ItemStack get(int index) {
|
||||||
|
if (index != 0) throw new IndexOutOfBoundsException("Inventory only has one slot");
|
||||||
|
return item;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int size() {
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<ItemStack> getItems() {
|
||||||
|
return itemView;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setChanged() {
|
||||||
|
// Should never happen, so a no-op.
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean stillValid(Player player) {
|
||||||
|
return !isRemoved();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@link ContainerData} for a {@link PrintoutMenu}. This provides a read/write view of the current page.
|
||||||
|
*/
|
||||||
|
private final class PrintoutContainerData implements SingleContainerData {
|
||||||
|
@Override
|
||||||
|
public int get() {
|
||||||
|
return page;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void set(int index, int value) {
|
||||||
|
if (index == 0) setPage(value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@@ -0,0 +1,137 @@
|
|||||||
|
// SPDX-FileCopyrightText: 2024 The CC: Tweaked Developers
|
||||||
|
//
|
||||||
|
// SPDX-License-Identifier: MPL-2.0
|
||||||
|
|
||||||
|
package dan200.computercraft.shared.media;
|
||||||
|
|
||||||
|
import dan200.computercraft.shared.ModRegistry;
|
||||||
|
import dan200.computercraft.shared.container.InvisibleSlot;
|
||||||
|
import dan200.computercraft.shared.media.items.PrintoutData;
|
||||||
|
import dan200.computercraft.shared.media.items.PrintoutItem;
|
||||||
|
import net.minecraft.util.Mth;
|
||||||
|
import net.minecraft.world.Container;
|
||||||
|
import net.minecraft.world.InteractionHand;
|
||||||
|
import net.minecraft.world.SimpleContainer;
|
||||||
|
import net.minecraft.world.entity.player.Inventory;
|
||||||
|
import net.minecraft.world.entity.player.Player;
|
||||||
|
import net.minecraft.world.inventory.*;
|
||||||
|
import net.minecraft.world.item.ItemStack;
|
||||||
|
|
||||||
|
import java.util.function.Predicate;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The menus for {@linkplain PrintoutItem printouts}.
|
||||||
|
* <p>
|
||||||
|
* This is a somewhat similar design to {@link LecternMenu}, which is used to read written books.
|
||||||
|
* <p>
|
||||||
|
* This holds a single slot (containing the printout), and a single data slot ({@linkplain #DATA_CURRENT_PAGE holding
|
||||||
|
* the current page}). The page is set by the client by sending a {@linkplain #clickMenuButton(Player, int) button
|
||||||
|
* press} with an index of {@link #PAGE_BUTTON_OFFSET} plus the current page.
|
||||||
|
* <p>
|
||||||
|
* The client-side screen uses {@linkplain ContainerListener container listeners} to subscribe to item and page changes.
|
||||||
|
* However, listeners aren't fired on the client, so we copy {@link LecternMenu}'s hack and call
|
||||||
|
* {@link #broadcastChanges()} whenever an item or data value are changed.
|
||||||
|
*/
|
||||||
|
public class PrintoutMenu extends AbstractContainerMenu {
|
||||||
|
public static final int DATA_CURRENT_PAGE = 0;
|
||||||
|
private static final int DATA_SIZE = 1;
|
||||||
|
|
||||||
|
public static final int PAGE_BUTTON_OFFSET = 100;
|
||||||
|
|
||||||
|
private final Predicate<Player> valid;
|
||||||
|
private final ContainerData currentPage;
|
||||||
|
|
||||||
|
public PrintoutMenu(
|
||||||
|
int containerId, Container container, int slotIdx, Predicate<Player> valid, ContainerData currentPage
|
||||||
|
) {
|
||||||
|
super(ModRegistry.Menus.PRINTOUT.get(), containerId);
|
||||||
|
this.valid = valid;
|
||||||
|
this.currentPage = currentPage;
|
||||||
|
|
||||||
|
addSlot(new InvisibleSlot(container, slotIdx) {
|
||||||
|
@Override
|
||||||
|
public void setChanged() {
|
||||||
|
super.setChanged();
|
||||||
|
slotsChanged(container); // Trigger listeners on the client.
|
||||||
|
}
|
||||||
|
});
|
||||||
|
addDataSlots(currentPage);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create {@link PrintoutMenu} for use a remote (client).
|
||||||
|
*
|
||||||
|
* @param containerId The current container id.
|
||||||
|
* @return The constructed container.
|
||||||
|
*/
|
||||||
|
public static PrintoutMenu createRemote(int containerId) {
|
||||||
|
return new PrintoutMenu(containerId, new SimpleContainer(1), 0, p -> true, new SimpleContainerData(DATA_SIZE));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a {@link PrintoutMenu} for the printout in the current player's hand.
|
||||||
|
*
|
||||||
|
* @param containerId The current container id.
|
||||||
|
* @param player The player to open the container.
|
||||||
|
* @param hand The hand containing the item.
|
||||||
|
* @return The constructed container.
|
||||||
|
*/
|
||||||
|
public static PrintoutMenu createInHand(int containerId, Player player, InteractionHand hand) {
|
||||||
|
var currentStack = player.getItemInHand(hand);
|
||||||
|
var currentItem = currentStack.getItem();
|
||||||
|
|
||||||
|
var slot = switch (hand) {
|
||||||
|
case MAIN_HAND -> player.getInventory().selected;
|
||||||
|
case OFF_HAND -> Inventory.SLOT_OFFHAND;
|
||||||
|
};
|
||||||
|
return new PrintoutMenu(
|
||||||
|
containerId, player.getInventory(), slot,
|
||||||
|
p -> player.getItemInHand(hand).getItem() == currentItem, new SimpleContainerData(DATA_SIZE)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ItemStack quickMoveStack(Player player, int index) {
|
||||||
|
return ItemStack.EMPTY;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean stillValid(Player player) {
|
||||||
|
return valid.test(player);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean clickMenuButton(Player player, int id) {
|
||||||
|
if (id >= PAGE_BUTTON_OFFSET) {
|
||||||
|
var page = Mth.clamp(id - PAGE_BUTTON_OFFSET, 0, PrintoutData.getOrEmpty(getPrintout()).pages() - 1);
|
||||||
|
setData(DATA_CURRENT_PAGE, page);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return super.clickMenuButton(player, id);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the current printout.
|
||||||
|
*
|
||||||
|
* @return The current printout.
|
||||||
|
*/
|
||||||
|
public ItemStack getPrintout() {
|
||||||
|
return getSlot(0).getItem();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the current page.
|
||||||
|
*
|
||||||
|
* @return The current page.
|
||||||
|
*/
|
||||||
|
public int getPage() {
|
||||||
|
return currentPage.get(DATA_CURRENT_PAGE);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setData(int id, int data) {
|
||||||
|
super.setData(id, data);
|
||||||
|
broadcastChanges(); // Trigger listeners on the client.
|
||||||
|
}
|
||||||
|
}
|
@@ -4,13 +4,13 @@
|
|||||||
|
|
||||||
package dan200.computercraft.shared.media.items;
|
package dan200.computercraft.shared.media.items;
|
||||||
|
|
||||||
import dan200.computercraft.shared.ModRegistry;
|
import com.google.common.base.Strings;
|
||||||
import dan200.computercraft.shared.common.HeldItemMenu;
|
import dan200.computercraft.shared.media.PrintoutMenu;
|
||||||
import dan200.computercraft.shared.network.container.HeldItemContainerData;
|
|
||||||
import net.minecraft.network.chat.Component;
|
import net.minecraft.network.chat.Component;
|
||||||
import net.minecraft.world.InteractionHand;
|
import net.minecraft.world.InteractionHand;
|
||||||
import net.minecraft.world.InteractionResult;
|
import net.minecraft.world.InteractionResult;
|
||||||
import net.minecraft.world.InteractionResultHolder;
|
import net.minecraft.world.InteractionResultHolder;
|
||||||
|
import net.minecraft.world.SimpleMenuProvider;
|
||||||
import net.minecraft.world.entity.player.Player;
|
import net.minecraft.world.entity.player.Player;
|
||||||
import net.minecraft.world.item.Item;
|
import net.minecraft.world.item.Item;
|
||||||
import net.minecraft.world.item.ItemStack;
|
import net.minecraft.world.item.ItemStack;
|
||||||
@@ -41,11 +41,13 @@ public class PrintoutItem extends Item {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public InteractionResultHolder<ItemStack> use(Level world, Player player, InteractionHand hand) {
|
public InteractionResultHolder<ItemStack> use(Level world, Player player, InteractionHand hand) {
|
||||||
|
var stack = player.getItemInHand(hand);
|
||||||
if (!world.isClientSide) {
|
if (!world.isClientSide) {
|
||||||
new HeldItemContainerData(hand)
|
var title = PrintoutData.getOrEmpty(stack).title();
|
||||||
.open(player, new HeldItemMenu.Factory(ModRegistry.Menus.PRINTOUT.get(), player.getItemInHand(hand), hand));
|
var displayTitle = Strings.isNullOrEmpty(title) ? stack.getDisplayName() : Component.literal(title);
|
||||||
|
player.openMenu(new SimpleMenuProvider((id, playerInventory, p) -> PrintoutMenu.createInHand(id, p, hand), displayTitle));
|
||||||
}
|
}
|
||||||
return new InteractionResultHolder<>(InteractionResult.sidedSuccess(world.isClientSide), player.getItemInHand(hand));
|
return new InteractionResultHolder<>(InteractionResult.sidedSuccess(world.isClientSide), stack);
|
||||||
}
|
}
|
||||||
|
|
||||||
public Type getType() {
|
public Type getType() {
|
||||||
|
@@ -1,31 +0,0 @@
|
|||||||
// SPDX-FileCopyrightText: 2019 The CC: Tweaked Developers
|
|
||||||
//
|
|
||||||
// SPDX-License-Identifier: MPL-2.0
|
|
||||||
|
|
||||||
package dan200.computercraft.shared.network.container;
|
|
||||||
|
|
||||||
import dan200.computercraft.shared.common.HeldItemMenu;
|
|
||||||
import dan200.computercraft.shared.media.items.PrintoutItem;
|
|
||||||
import dan200.computercraft.shared.network.codec.MoreStreamCodecs;
|
|
||||||
import net.minecraft.network.RegistryFriendlyByteBuf;
|
|
||||||
import net.minecraft.network.codec.StreamCodec;
|
|
||||||
import net.minecraft.world.InteractionHand;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Opens a printout GUI based on the currently held item.
|
|
||||||
*
|
|
||||||
* @param hand The hand holding this item.
|
|
||||||
* @see HeldItemMenu
|
|
||||||
* @see PrintoutItem
|
|
||||||
*/
|
|
||||||
public record HeldItemContainerData(InteractionHand hand) implements ContainerData {
|
|
||||||
public static final StreamCodec<RegistryFriendlyByteBuf, HeldItemContainerData> STREAM_CODEC = StreamCodec.composite(
|
|
||||||
MoreStreamCodecs.ofEnum(InteractionHand.class), HeldItemContainerData::hand,
|
|
||||||
HeldItemContainerData::new
|
|
||||||
);
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void toBytes(RegistryFriendlyByteBuf buf) {
|
|
||||||
STREAM_CODEC.encode(buf, this);
|
|
||||||
}
|
|
||||||
}
|
|
@@ -99,11 +99,13 @@ public final class DiskDriveBlockEntity extends AbstractContainerBlockEntity imp
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void clearRemoved() {
|
public void clearRemoved() {
|
||||||
|
super.clearRemoved();
|
||||||
updateMedia();
|
updateMedia();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void setRemoved() {
|
public void setRemoved() {
|
||||||
|
super.setRemoved();
|
||||||
if (recordPlaying) stopRecord();
|
if (recordPlaying) stopRecord();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -70,10 +70,10 @@ public abstract class AbstractFluidMethods<T> implements GenericPeripheral {
|
|||||||
) throws LuaException;
|
) throws LuaException;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Move a fluid from a connected fluid container into this oneone.
|
* Move a fluid from a connected fluid container into this one.
|
||||||
* <p>
|
* <p>
|
||||||
* This allows you to pull fluid in the current fluid container from another container <em>on the same wired
|
* This allows you to pull fluid in the current fluid container from another container <em>on the same wired
|
||||||
* network</em>. Both containers must attached to wired modems which are connected via a cable.
|
* network</em>. Both containers must be attached to wired modems which are connected via a cable.
|
||||||
*
|
*
|
||||||
* @param to Container to move fluid to.
|
* @param to Container to move fluid to.
|
||||||
* @param computer The current computer.
|
* @param computer The current computer.
|
||||||
|
@@ -30,6 +30,12 @@ import java.util.Objects;
|
|||||||
* print("On something else")
|
* print("On something else")
|
||||||
* end
|
* end
|
||||||
* }</pre>
|
* }</pre>
|
||||||
|
* <p>
|
||||||
|
* ## Recipes
|
||||||
|
* <div class="recipe-container">
|
||||||
|
* <mc-recipe recipe="computercraft:pocket_computer_normal"></mc-recipe>
|
||||||
|
* <mc-recipe recipe="computercraft:pocket_computer_advanced"></mc-recipe>
|
||||||
|
* </div>
|
||||||
*
|
*
|
||||||
* @cc.module pocket
|
* @cc.module pocket
|
||||||
*/
|
*/
|
||||||
|
@@ -102,12 +102,16 @@ public class PocketComputerItem extends Item implements IMedia {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void inventoryTick(ItemStack stack, Level world, Entity entity, int slotNum, boolean selected) {
|
public void inventoryTick(ItemStack stack, Level world, Entity entity, int compartmentSlot, boolean selected) {
|
||||||
// This (in vanilla at least) is only called for players. Don't bother to handle other entities.
|
// This (in vanilla at least) is only called for players. Don't bother to handle other entities.
|
||||||
if (world.isClientSide || !(entity instanceof ServerPlayer player)) return;
|
if (world.isClientSide || !(entity instanceof ServerPlayer player)) return;
|
||||||
|
|
||||||
|
// Find the actual slot the item exists in, aborting if it can't be found.
|
||||||
|
var slot = InventoryUtil.getInventorySlotFromCompartment(player, compartmentSlot, stack);
|
||||||
|
if (slot < 0) return;
|
||||||
|
|
||||||
// If we're in the inventory, create a computer and keep it alive.
|
// If we're in the inventory, create a computer and keep it alive.
|
||||||
var holder = new PocketHolder.PlayerHolder(player, slotNum);
|
var holder = new PocketHolder.PlayerHolder(player, slot);
|
||||||
var brain = getOrCreateBrain((ServerLevel) world, holder, stack);
|
var brain = getOrCreateBrain((ServerLevel) world, holder, stack);
|
||||||
brain.computer().keepAlive();
|
brain.computer().keepAlive();
|
||||||
|
|
||||||
|
@@ -47,18 +47,24 @@ import java.util.Optional;
|
|||||||
* <p>
|
* <p>
|
||||||
* ## Turtle upgrades
|
* ## Turtle upgrades
|
||||||
* While a normal turtle can move about the world and place blocks, its functionality is limited. Thankfully, turtles
|
* While a normal turtle can move about the world and place blocks, its functionality is limited. Thankfully, turtles
|
||||||
* can be upgraded with *tools* and [peripherals][`peripheral`]. Turtles have two upgrade slots, one on the left and right
|
* can be upgraded with upgrades. Turtles have two upgrade slots, one on the left and right sides. Upgrades can be
|
||||||
* sides. Upgrades can be equipped by crafting a turtle with the upgrade, or calling the [`turtle.equipLeft`]/[`turtle.equipRight`]
|
* equipped by crafting a turtle with the upgrade, or calling the [`turtle.equipLeft`]/[`turtle.equipRight`] functions.
|
||||||
* functions.
|
|
||||||
* <p>
|
* <p>
|
||||||
* Turtle tools allow you to break blocks ([`turtle.dig`]) and attack entities ([`turtle.attack`]). Some tools are more
|
* By default, any diamond tool may be used as an upgrade (though more may be added with [datapacks]). The diamond
|
||||||
* suitable to a task than others. For instance, a diamond pickaxe can break every block, while a sword does more
|
* pickaxe may be used to break blocks (with [`turtle.dig`]), while the sword can attack entities ([`turtle.attack`]).
|
||||||
* damage. Other tools have more niche use-cases, for instance hoes can til dirt.
|
* Other tools have more niche use-cases, for instance hoes can til dirt.
|
||||||
* <p>
|
* <p>
|
||||||
* Peripherals (such as the [wireless modem][`modem`] or [`speaker`]) can also be equipped as upgrades. These are then
|
* Some peripherals (namely [speakers][`speaker`] and Ender and Wireless [modems][`modem`]) can also be equipped as
|
||||||
* accessible by accessing the `"left"` or `"right"` peripheral.
|
* upgrades. These are then accessible by accessing the `"left"` or `"right"` peripheral.
|
||||||
|
* <p>
|
||||||
|
* ## Recipes
|
||||||
|
* <div class="recipe-container">
|
||||||
|
* <mc-recipe recipe="computercraft:turtle_normal"></mc-recipe>
|
||||||
|
* <mc-recipe recipe="computercraft:turtle_advanced"></mc-recipe>
|
||||||
|
* </div>
|
||||||
* <p>
|
* <p>
|
||||||
* [Turtle Graphics]: https://en.wikipedia.org/wiki/Turtle_graphics "Turtle graphics"
|
* [Turtle Graphics]: https://en.wikipedia.org/wiki/Turtle_graphics "Turtle graphics"
|
||||||
|
* [datapacks]: https://datapacks.madefor.cc ""
|
||||||
*
|
*
|
||||||
* @cc.module turtle
|
* @cc.module turtle
|
||||||
* @cc.since 1.3
|
* @cc.since 1.3
|
||||||
@@ -345,7 +351,7 @@ public class TurtleAPI implements ILuaAPI {
|
|||||||
* For instance, if a slot contains 13 blocks of dirt, it has room for another 51.
|
* For instance, if a slot contains 13 blocks of dirt, it has room for another 51.
|
||||||
*
|
*
|
||||||
* @param slot The slot we wish to check. Defaults to the {@link #select selected slot}.
|
* @param slot The slot we wish to check. Defaults to the {@link #select selected slot}.
|
||||||
* @return The space left in in this slot.
|
* @return The space left in this slot.
|
||||||
* @throws LuaException If the slot is out of range.
|
* @throws LuaException If the slot is out of range.
|
||||||
*/
|
*/
|
||||||
@LuaFunction
|
@LuaFunction
|
||||||
|
@@ -129,11 +129,15 @@ public class TurtleBlock extends AbstractComputerBlock<TurtleBlockEntity> implem
|
|||||||
protected final void onRemove(BlockState state, Level level, BlockPos pos, BlockState newState, boolean isMoving) {
|
protected final void onRemove(BlockState state, Level level, BlockPos pos, BlockState newState, boolean isMoving) {
|
||||||
if (state.is(newState.getBlock())) return;
|
if (state.is(newState.getBlock())) return;
|
||||||
|
|
||||||
if (!level.isClientSide && level.getBlockEntity(pos) instanceof TurtleBlockEntity turtle && !turtle.hasMoved()) {
|
// Most blocks drop items and then remove the BE. However, if a turtle is consuming drops right now, that can
|
||||||
Containers.dropContents(level, pos, turtle);
|
// lead to loops where it tries to insert an item back into the inventory. To prevent this, take a reference to
|
||||||
}
|
// the turtle BE now, remove it, and then drop the items.
|
||||||
|
var turtle = !level.isClientSide && level.getBlockEntity(pos) instanceof TurtleBlockEntity t && !t.hasMoved()
|
||||||
|
? t : null;
|
||||||
|
|
||||||
super.onRemove(state, level, pos, newState, isMoving);
|
super.onRemove(state, level, pos, newState, isMoving);
|
||||||
|
|
||||||
|
if (turtle != null) Containers.dropContents(level, pos, turtle);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@@ -48,7 +48,7 @@ public class ComponentizationFixers {
|
|||||||
|
|
||||||
private static final Set<String> DYEABLE = Stream.concat(
|
private static final Set<String> DYEABLE = Stream.concat(
|
||||||
Stream.of(TURTLES, POCKET_COMPUTERS).flatMap(Set::stream),
|
Stream.of(TURTLES, POCKET_COMPUTERS).flatMap(Set::stream),
|
||||||
Stream.of(DISK, TREASURE_DISK)
|
Stream.of(DISK)
|
||||||
).collect(Collectors.toUnmodifiableSet());
|
).collect(Collectors.toUnmodifiableSet());
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -62,13 +62,7 @@ public class ComponentizationFixers {
|
|||||||
if (item.is(ALL_COMPUTERS)) item.moveTagToComponent("ComputerId", "computercraft:computer_id");
|
if (item.is(ALL_COMPUTERS)) item.moveTagToComponent("ComputerId", "computercraft:computer_id");
|
||||||
|
|
||||||
// Set dyed colour
|
// Set dyed colour
|
||||||
if (item.is(DYEABLE)) {
|
if (item.is(DYEABLE)) moveColourToComponent(item, ops, "Color");
|
||||||
item.removeTag("Color").asNumber().result().map(Number::intValue).ifPresent(col ->
|
|
||||||
item.setComponent("minecraft:dyed_color", ops.emptyMap()
|
|
||||||
.set("rgb", ops.createInt(col))
|
|
||||||
.set("show_in_tooltip", ops.createBoolean(false))
|
|
||||||
));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (item.is(POCKET_COMPUTERS)) {
|
if (item.is(POCKET_COMPUTERS)) {
|
||||||
item.moveTagToComponent("On", "computercraft:on");
|
item.moveTagToComponent("On", "computercraft:on");
|
||||||
@@ -89,7 +83,7 @@ public class ComponentizationFixers {
|
|||||||
moveUpgradeToComponent(item, ops, "RightUpgrade", "RightUpgradeNbt", "computercraft:right_turtle_upgrade");
|
moveUpgradeToComponent(item, ops, "RightUpgrade", "RightUpgradeNbt", "computercraft:right_turtle_upgrade");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (item.is(DISK)) item.moveTagToComponent("DiskId", "computercraft:disk");
|
if (item.is(DISK)) item.moveTagToComponent("DiskId", "computercraft:disk_id");
|
||||||
|
|
||||||
if (item.is(TREASURE_DISK)) {
|
if (item.is(TREASURE_DISK)) {
|
||||||
var name = item.removeTag("Title").asString().result();
|
var name = item.removeTag("Title").asString().result();
|
||||||
@@ -99,6 +93,8 @@ public class ComponentizationFixers {
|
|||||||
.set("name", ops.createString(name.get()))
|
.set("name", ops.createString(name.get()))
|
||||||
.set("path", ops.createString(path.get())));
|
.set("path", ops.createString(path.get())));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
moveColourToComponent(item, ops, "Colour");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (item.is(PRINTOUTS)) movePrintoutToComponent(item, ops);
|
if (item.is(PRINTOUTS)) movePrintoutToComponent(item, ops);
|
||||||
@@ -111,6 +107,14 @@ public class ComponentizationFixers {
|
|||||||
data.setComponent(component, createUpgradeData(ops, upgrade, data.removeTag(dataKey)));
|
data.setComponent(component, createUpgradeData(ops, upgrade, data.removeTag(dataKey)));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static void moveColourToComponent(ItemStackComponentizationFix.ItemStackData item, Dynamic<?> ops, String key) {
|
||||||
|
item.removeTag(key).asNumber().result().map(Number::intValue).ifPresent(col ->
|
||||||
|
item.setComponent("minecraft:dyed_color", ops.emptyMap()
|
||||||
|
.set("rgb", ops.createInt(col))
|
||||||
|
.set("show_in_tooltip", ops.createBoolean(false))
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Move printout data to a component.
|
* Move printout data to a component.
|
||||||
*
|
*
|
||||||
|
@@ -9,9 +9,12 @@ import net.minecraft.core.Direction;
|
|||||||
import net.minecraft.server.level.ServerLevel;
|
import net.minecraft.server.level.ServerLevel;
|
||||||
import net.minecraft.world.Container;
|
import net.minecraft.world.Container;
|
||||||
import net.minecraft.world.InteractionHand;
|
import net.minecraft.world.InteractionHand;
|
||||||
|
import net.minecraft.world.entity.Entity;
|
||||||
import net.minecraft.world.entity.player.Inventory;
|
import net.minecraft.world.entity.player.Inventory;
|
||||||
import net.minecraft.world.entity.player.Player;
|
import net.minecraft.world.entity.player.Player;
|
||||||
|
import net.minecraft.world.item.Item;
|
||||||
import net.minecraft.world.item.ItemStack;
|
import net.minecraft.world.item.ItemStack;
|
||||||
|
import net.minecraft.world.level.Level;
|
||||||
import net.minecraft.world.phys.EntityHitResult;
|
import net.minecraft.world.phys.EntityHitResult;
|
||||||
import net.minecraft.world.phys.Vec3;
|
import net.minecraft.world.phys.Vec3;
|
||||||
|
|
||||||
@@ -35,6 +38,28 @@ public final class InventoryUtil {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Map a slot inside a player's compartment to a slot in the full player's inventory.
|
||||||
|
* <p>
|
||||||
|
* {@link Inventory#tick()} passes in a slot to {@link Item#inventoryTick(ItemStack, Level, Entity, int, boolean)}.
|
||||||
|
* However, this slot corresponds to the index within the current compartment (items, armour, offhand) and not
|
||||||
|
* the actual slot.
|
||||||
|
* <p>
|
||||||
|
* This method searches the relevant compartments (inventory and offhand, skipping armour) for the stack, returning
|
||||||
|
* its slot if found.
|
||||||
|
*
|
||||||
|
* @param player The player holding the item.
|
||||||
|
* @param slot The slot inside the compartment.
|
||||||
|
* @param stack The stack being ticked.
|
||||||
|
* @return The inventory slot, or {@code -1} if the item could not be found in the inventory.
|
||||||
|
*/
|
||||||
|
public static int getInventorySlotFromCompartment(Player player, int slot, ItemStack stack) {
|
||||||
|
if (stack.isEmpty()) throw new IllegalArgumentException("Cannot search for empty stack");
|
||||||
|
if (player.getInventory().getItem(slot) == stack) return slot;
|
||||||
|
if (player.getInventory().getItem(Inventory.SLOT_OFFHAND) == stack) return Inventory.SLOT_OFFHAND;
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
public static @Nullable Container getEntityContainer(ServerLevel level, BlockPos pos, Direction side) {
|
public static @Nullable Container getEntityContainer(ServerLevel level, BlockPos pos, Direction side) {
|
||||||
var vecStart = new Vec3(
|
var vecStart = new Vec3(
|
||||||
pos.getX() + 0.5 + 0.6 * side.getStepX(),
|
pos.getX() + 0.5 + 0.6 * side.getStepX(),
|
||||||
|
@@ -109,8 +109,9 @@ public final class TickScheduler {
|
|||||||
return State.UNLOADED;
|
return State.UNLOADED;
|
||||||
} else {
|
} else {
|
||||||
// This should be impossible: either the block entity is at the above position, or it has been removed.
|
// This should be impossible: either the block entity is at the above position, or it has been removed.
|
||||||
if (level.getBlockEntity(pos) != blockEntity) {
|
var currentBlockEntity = level.getBlockEntity(pos);
|
||||||
throw new IllegalStateException("Expected " + blockEntity + " at " + pos);
|
if (currentBlockEntity != blockEntity) {
|
||||||
|
throw new IllegalStateException("Expected " + blockEntity + " at " + pos + ", got " + currentBlockEntity);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Otherwise schedule a tick and remove it from the queue.
|
// Otherwise schedule a tick and remove it from the queue.
|
||||||
|
Binary file not shown.
After Width: | Height: | Size: 212 B |
@@ -0,0 +1,3 @@
|
|||||||
|
SPDX-FileCopyrightText: 2024 The CC: Tweaked Developers
|
||||||
|
|
||||||
|
SPDX-License-Identifier: MPL-2.0
|
@@ -6,16 +6,11 @@ package dan200.computercraft.mixin.gametest;
|
|||||||
|
|
||||||
import net.minecraft.gametest.framework.GameTestHelper;
|
import net.minecraft.gametest.framework.GameTestHelper;
|
||||||
import net.minecraft.gametest.framework.GameTestInfo;
|
import net.minecraft.gametest.framework.GameTestInfo;
|
||||||
import net.minecraft.world.phys.AABB;
|
|
||||||
import org.spongepowered.asm.mixin.Mixin;
|
import org.spongepowered.asm.mixin.Mixin;
|
||||||
import org.spongepowered.asm.mixin.gen.Accessor;
|
import org.spongepowered.asm.mixin.gen.Accessor;
|
||||||
import org.spongepowered.asm.mixin.gen.Invoker;
|
|
||||||
|
|
||||||
@Mixin(GameTestHelper.class)
|
@Mixin(GameTestHelper.class)
|
||||||
public interface GameTestHelperAccessor {
|
public interface GameTestHelperAccessor {
|
||||||
@Invoker
|
|
||||||
AABB callGetBounds();
|
|
||||||
|
|
||||||
@Accessor
|
@Accessor
|
||||||
GameTestInfo getTestInfo();
|
GameTestInfo getTestInfo();
|
||||||
}
|
}
|
||||||
|
@@ -19,9 +19,11 @@ import net.minecraft.gametest.framework.GameTest
|
|||||||
import net.minecraft.gametest.framework.GameTestHelper
|
import net.minecraft.gametest.framework.GameTestHelper
|
||||||
import net.minecraft.world.item.ItemStack
|
import net.minecraft.world.item.ItemStack
|
||||||
import net.minecraft.world.item.Items
|
import net.minecraft.world.item.Items
|
||||||
|
import net.minecraft.world.level.Level
|
||||||
import net.minecraft.world.level.block.Blocks
|
import net.minecraft.world.level.block.Blocks
|
||||||
import net.minecraft.world.level.block.LeverBlock
|
import net.minecraft.world.level.block.LeverBlock
|
||||||
import net.minecraft.world.level.block.RedstoneLampBlock
|
import net.minecraft.world.level.block.RedstoneLampBlock
|
||||||
|
import net.minecraft.world.phys.Vec3
|
||||||
import org.junit.jupiter.api.Assertions.assertEquals
|
import org.junit.jupiter.api.Assertions.assertEquals
|
||||||
import org.junit.jupiter.api.Assertions.assertTrue
|
import org.junit.jupiter.api.Assertions.assertTrue
|
||||||
import org.lwjgl.glfw.GLFW
|
import org.lwjgl.glfw.GLFW
|
||||||
@@ -115,6 +117,19 @@ class Computer_Test {
|
|||||||
thenOnComputer { callPeripheral("right", "size").assertArrayEquals(54) }
|
thenOnComputer { callPeripheral("right", "size").assertArrayEquals(54) }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tests a computer item is dropped on explosion.
|
||||||
|
*/
|
||||||
|
@GameTest
|
||||||
|
fun Drops_on_explosion(context: GameTestHelper) = context.sequence {
|
||||||
|
thenExecute {
|
||||||
|
val explosionPos = Vec3.atCenterOf(context.absolutePos(BlockPos(2, 2, 2)))
|
||||||
|
context.level.explode(null, explosionPos.x, explosionPos.y, explosionPos.z, 2.0f, Level.ExplosionInteraction.TNT)
|
||||||
|
|
||||||
|
context.assertItemEntityCountIs(ModRegistry.Items.COMPUTER_NORMAL.get(), 1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Check the client can open the computer UI and interact with it.
|
* Check the client can open the computer UI and interact with it.
|
||||||
*/
|
*/
|
||||||
|
@@ -7,19 +7,23 @@ package dan200.computercraft.gametest
|
|||||||
import dan200.computercraft.core.apis.FSAPI
|
import dan200.computercraft.core.apis.FSAPI
|
||||||
import dan200.computercraft.gametest.api.*
|
import dan200.computercraft.gametest.api.*
|
||||||
import dan200.computercraft.shared.ModRegistry
|
import dan200.computercraft.shared.ModRegistry
|
||||||
|
import dan200.computercraft.shared.media.items.TreasureDisk
|
||||||
import dan200.computercraft.shared.peripheral.diskdrive.DiskDriveBlock
|
import dan200.computercraft.shared.peripheral.diskdrive.DiskDriveBlock
|
||||||
import dan200.computercraft.shared.peripheral.diskdrive.DiskDrivePeripheral
|
import dan200.computercraft.shared.peripheral.diskdrive.DiskDrivePeripheral
|
||||||
import dan200.computercraft.shared.peripheral.diskdrive.DiskDriveState
|
import dan200.computercraft.shared.peripheral.diskdrive.DiskDriveState
|
||||||
import dan200.computercraft.shared.util.DataComponentUtil
|
import dan200.computercraft.shared.util.DataComponentUtil
|
||||||
|
import dan200.computercraft.shared.util.NonNegativeId
|
||||||
import dan200.computercraft.test.core.assertArrayEquals
|
import dan200.computercraft.test.core.assertArrayEquals
|
||||||
import dan200.computercraft.test.core.computer.getApi
|
import dan200.computercraft.test.core.computer.getApi
|
||||||
import net.minecraft.core.BlockPos
|
import net.minecraft.core.BlockPos
|
||||||
|
import net.minecraft.core.component.DataComponentPatch
|
||||||
import net.minecraft.core.component.DataComponents
|
import net.minecraft.core.component.DataComponents
|
||||||
import net.minecraft.gametest.framework.GameTest
|
import net.minecraft.gametest.framework.GameTest
|
||||||
import net.minecraft.gametest.framework.GameTestHelper
|
import net.minecraft.gametest.framework.GameTestHelper
|
||||||
import net.minecraft.network.chat.Component
|
import net.minecraft.network.chat.Component
|
||||||
import net.minecraft.world.item.ItemStack
|
import net.minecraft.world.item.ItemStack
|
||||||
import net.minecraft.world.item.Items
|
import net.minecraft.world.item.Items
|
||||||
|
import net.minecraft.world.item.component.DyedItemColor
|
||||||
import net.minecraft.world.level.block.RedStoneWireBlock
|
import net.minecraft.world.level.block.RedStoneWireBlock
|
||||||
import org.hamcrest.MatcherAssert.assertThat
|
import org.hamcrest.MatcherAssert.assertThat
|
||||||
import org.hamcrest.Matchers.array
|
import org.hamcrest.Matchers.array
|
||||||
@@ -189,4 +193,40 @@ class Disk_Drive_Test {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Loads a structure created on an older version of the game, and checks that data fixers have been applied.
|
||||||
|
*/
|
||||||
|
@GameTest
|
||||||
|
fun Data_fixers(helper: GameTestHelper) = helper.sequence {
|
||||||
|
thenExecute {
|
||||||
|
helper.assertContainerExactly(
|
||||||
|
BlockPos(1, 2, 2),
|
||||||
|
listOf(
|
||||||
|
ItemStack(ModRegistry.Items.DISK.get()).also {
|
||||||
|
it.applyComponents(
|
||||||
|
DataComponentPatch.builder()
|
||||||
|
.set(ModRegistry.DataComponents.DISK_ID.get(), NonNegativeId(123))
|
||||||
|
.set(DataComponents.DYED_COLOR, DyedItemColor(123456, false))
|
||||||
|
.build(),
|
||||||
|
)
|
||||||
|
},
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
helper.assertContainerExactly(
|
||||||
|
BlockPos(3, 2, 2),
|
||||||
|
listOf(
|
||||||
|
ItemStack(ModRegistry.Items.TREASURE_DISK.get()).also {
|
||||||
|
it.applyComponents(
|
||||||
|
DataComponentPatch.builder()
|
||||||
|
.set(ModRegistry.DataComponents.TREASURE_DISK.get(), TreasureDisk("Demo disk", "demo"))
|
||||||
|
.set(DataComponents.DYED_COLOR, DyedItemColor(123456, false))
|
||||||
|
.build(),
|
||||||
|
)
|
||||||
|
},
|
||||||
|
),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@@ -4,19 +4,27 @@
|
|||||||
|
|
||||||
package dan200.computercraft.gametest
|
package dan200.computercraft.gametest
|
||||||
|
|
||||||
|
import dan200.computercraft.api.ComputerCraftAPI
|
||||||
import dan200.computercraft.api.lua.Coerced
|
import dan200.computercraft.api.lua.Coerced
|
||||||
|
import dan200.computercraft.api.pocket.IPocketUpgrade
|
||||||
|
import dan200.computercraft.api.upgrades.UpgradeData
|
||||||
import dan200.computercraft.client.pocket.ClientPocketComputers
|
import dan200.computercraft.client.pocket.ClientPocketComputers
|
||||||
import dan200.computercraft.core.apis.TermAPI
|
import dan200.computercraft.core.apis.TermAPI
|
||||||
import dan200.computercraft.gametest.api.*
|
import dan200.computercraft.gametest.api.*
|
||||||
import dan200.computercraft.mixin.gametest.GameTestHelperAccessor
|
import dan200.computercraft.mixin.gametest.GameTestHelperAccessor
|
||||||
import dan200.computercraft.shared.ModRegistry
|
import dan200.computercraft.shared.ModRegistry
|
||||||
import dan200.computercraft.shared.computer.core.ComputerState
|
import dan200.computercraft.shared.computer.core.ComputerState
|
||||||
|
import dan200.computercraft.shared.util.DataComponentUtil
|
||||||
|
import dan200.computercraft.shared.util.NonNegativeId
|
||||||
import dan200.computercraft.test.core.computer.getApi
|
import dan200.computercraft.test.core.computer.getApi
|
||||||
import net.minecraft.core.BlockPos
|
import net.minecraft.core.BlockPos
|
||||||
|
import net.minecraft.core.component.DataComponentPatch
|
||||||
import net.minecraft.core.component.DataComponents
|
import net.minecraft.core.component.DataComponents
|
||||||
|
import net.minecraft.gametest.framework.GameTest
|
||||||
import net.minecraft.gametest.framework.GameTestHelper
|
import net.minecraft.gametest.framework.GameTestHelper
|
||||||
import net.minecraft.gametest.framework.GameTestSequence
|
import net.minecraft.gametest.framework.GameTestSequence
|
||||||
import net.minecraft.network.chat.Component
|
import net.minecraft.network.chat.Component
|
||||||
|
import net.minecraft.resources.ResourceLocation
|
||||||
import net.minecraft.world.item.ItemStack
|
import net.minecraft.world.item.ItemStack
|
||||||
import org.junit.jupiter.api.Assertions.assertEquals
|
import org.junit.jupiter.api.Assertions.assertEquals
|
||||||
import kotlin.random.Random
|
import kotlin.random.Random
|
||||||
@@ -104,4 +112,31 @@ class Pocket_Computer_Test {
|
|||||||
item.set(ModRegistry.DataComponents.ON.get(), true)
|
item.set(ModRegistry.DataComponents.ON.get(), true)
|
||||||
player.inventory.setItem(0, item)
|
player.inventory.setItem(0, item)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Loads a structure created on an older version of the game, and checks that data fixers have been applied.
|
||||||
|
*/
|
||||||
|
@GameTest
|
||||||
|
fun Data_fixers(helper: GameTestHelper) = helper.sequence {
|
||||||
|
thenExecute {
|
||||||
|
val upgrade = helper.level.registryAccess().registryOrThrow(IPocketUpgrade.REGISTRY)
|
||||||
|
.getHolder(ResourceLocation.fromNamespaceAndPath(ComputerCraftAPI.MOD_ID, "wireless_modem_normal"))
|
||||||
|
.orElseThrow()
|
||||||
|
|
||||||
|
helper.assertContainerExactly(
|
||||||
|
BlockPos(2, 2, 2),
|
||||||
|
listOf(
|
||||||
|
ItemStack(ModRegistry.Items.POCKET_COMPUTER_ADVANCED.get()).also {
|
||||||
|
DataComponentUtil.setCustomName(it, "Test")
|
||||||
|
it.applyComponents(
|
||||||
|
DataComponentPatch.builder()
|
||||||
|
.set(ModRegistry.DataComponents.COMPUTER_ID.get(), NonNegativeId(123))
|
||||||
|
.set(ModRegistry.DataComponents.POCKET_UPGRADE.get(), UpgradeData.ofDefault(upgrade))
|
||||||
|
.build(),
|
||||||
|
)
|
||||||
|
},
|
||||||
|
),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@@ -9,6 +9,7 @@ import dan200.computercraft.gametest.api.assertExactlyItems
|
|||||||
import dan200.computercraft.gametest.api.getBlockEntity
|
import dan200.computercraft.gametest.api.getBlockEntity
|
||||||
import dan200.computercraft.gametest.api.sequence
|
import dan200.computercraft.gametest.api.sequence
|
||||||
import dan200.computercraft.shared.ModRegistry
|
import dan200.computercraft.shared.ModRegistry
|
||||||
|
import dan200.computercraft.shared.media.items.PrintoutData
|
||||||
import dan200.computercraft.shared.peripheral.printer.PrinterBlock
|
import dan200.computercraft.shared.peripheral.printer.PrinterBlock
|
||||||
import dan200.computercraft.shared.util.DataComponentUtil
|
import dan200.computercraft.shared.util.DataComponentUtil
|
||||||
import net.minecraft.core.BlockPos
|
import net.minecraft.core.BlockPos
|
||||||
@@ -19,6 +20,7 @@ import net.minecraft.network.chat.Component
|
|||||||
import net.minecraft.world.item.ItemStack
|
import net.minecraft.world.item.ItemStack
|
||||||
import net.minecraft.world.item.Items
|
import net.minecraft.world.item.Items
|
||||||
import net.minecraft.world.level.block.RedStoneWireBlock
|
import net.minecraft.world.level.block.RedStoneWireBlock
|
||||||
|
import org.junit.jupiter.api.Assertions.assertEquals
|
||||||
|
|
||||||
class Printer_Test {
|
class Printer_Test {
|
||||||
/**
|
/**
|
||||||
@@ -96,4 +98,21 @@ class Printer_Test {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Loads a structure created on an older version of the game, and checks that data fixers have been applied.
|
||||||
|
*/
|
||||||
|
@GameTest
|
||||||
|
fun Data_fixers(helper: GameTestHelper) = helper.sequence {
|
||||||
|
thenExecute {
|
||||||
|
val container = helper.getBlockEntity(BlockPos(2, 2, 2), ModRegistry.BlockEntities.PRINTER.get())
|
||||||
|
val contents = container.getItem(1)
|
||||||
|
assertEquals(ModRegistry.Items.PRINTED_PAGE.get(), contents.item)
|
||||||
|
|
||||||
|
val printout = contents[ModRegistry.DataComponents.PRINTOUT.get()] ?: PrintoutData.EMPTY
|
||||||
|
assertEquals("example.lua", printout.title)
|
||||||
|
assertEquals("This is an example page ", printout.lines[0].text)
|
||||||
|
assertEquals("3333333333333333333333333", printout.lines[0].foreground)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@@ -734,6 +734,21 @@ class Turtle_Test {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tests a turtle can break a block that explodes, causing the turtle itself to explode.
|
||||||
|
*
|
||||||
|
* @see [#585](https://github.com/cc-tweaked/CC-Tweaked/issues/585).
|
||||||
|
*/
|
||||||
|
@GameTest
|
||||||
|
fun Breaks_exploding_block(context: GameTestHelper) = context.sequence {
|
||||||
|
thenOnComputer { turtle.dig(Optional.empty()) }
|
||||||
|
thenIdle(2)
|
||||||
|
thenExecute {
|
||||||
|
context.assertItemEntityCountIs(ModRegistry.Items.TURTLE_NORMAL.get(), 1)
|
||||||
|
context.assertItemEntityCountIs(Items.BONE_BLOCK, 65)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Render turtles as an item.
|
* Render turtles as an item.
|
||||||
*/
|
*/
|
||||||
|
@@ -22,6 +22,7 @@ import net.minecraft.world.Container
|
|||||||
import net.minecraft.world.InteractionHand
|
import net.minecraft.world.InteractionHand
|
||||||
import net.minecraft.world.entity.Entity
|
import net.minecraft.world.entity.Entity
|
||||||
import net.minecraft.world.entity.EntityType
|
import net.minecraft.world.entity.EntityType
|
||||||
|
import net.minecraft.world.item.Item
|
||||||
import net.minecraft.world.item.ItemStack
|
import net.minecraft.world.item.ItemStack
|
||||||
import net.minecraft.world.item.context.UseOnContext
|
import net.minecraft.world.item.context.UseOnContext
|
||||||
import net.minecraft.world.level.GameType
|
import net.minecraft.world.level.GameType
|
||||||
@@ -202,6 +203,10 @@ fun GameTestHelper.assertContainerExactly(pos: BlockPos, items: List<ItemStack>)
|
|||||||
fun <T> GameTestHelper.assertContainerExactly(entity: T, items: List<ItemStack>) where T : Entity, T : Container =
|
fun <T> GameTestHelper.assertContainerExactly(entity: T, items: List<ItemStack>) where T : Entity, T : Container =
|
||||||
assertContainerExactlyImpl(entity.blockPosition(), entity, items)
|
assertContainerExactlyImpl(entity.blockPosition(), entity, items)
|
||||||
|
|
||||||
|
private fun ItemStack.toStringFull(): String = if (isEmpty) "<empty>" else "$count x $item$componentsPatch"
|
||||||
|
|
||||||
|
private fun formatItems(items: List<ItemStack>) = items.joinToString(", ") { it.toStringFull() }
|
||||||
|
|
||||||
private fun GameTestHelper.assertContainerExactlyImpl(pos: BlockPos, container: Container, items: List<ItemStack>) {
|
private fun GameTestHelper.assertContainerExactlyImpl(pos: BlockPos, container: Container, items: List<ItemStack>) {
|
||||||
val slot = (0 until container.containerSize).indexOfFirst { slot ->
|
val slot = (0 until container.containerSize).indexOfFirst { slot ->
|
||||||
val expected = if (slot >= items.size) ItemStack.EMPTY else items[slot]
|
val expected = if (slot >= items.size) ItemStack.EMPTY else items[slot]
|
||||||
@@ -209,11 +214,12 @@ private fun GameTestHelper.assertContainerExactlyImpl(pos: BlockPos, container:
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (slot >= 0) {
|
if (slot >= 0) {
|
||||||
|
val invItems = (0 until container.containerSize).map { container.getItem(it) }.dropLastWhile { it.isEmpty }
|
||||||
failVerbose(
|
failVerbose(
|
||||||
"""
|
"""
|
||||||
Items do not match (first mismatch at slot $slot).
|
Items do not match (first mismatch at slot $slot).
|
||||||
Expected: $items
|
Expected: ${formatItems(items)}
|
||||||
Container: ${(0 until container.containerSize).map { container.getItem(it) }.dropLastWhile { it.isEmpty }}
|
Container: ${formatItems(invItems)}
|
||||||
""".trimIndent(),
|
""".trimIndent(),
|
||||||
pos,
|
pos,
|
||||||
)
|
)
|
||||||
@@ -252,6 +258,16 @@ fun GameTestHelper.assertExactlyItems(vararg expected: ItemStack, message: Strin
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Similar to [GameTestHelper.assertItemEntityCountIs], but searching anywhere in the structure bounds.
|
||||||
|
*/
|
||||||
|
fun GameTestHelper.assertItemEntityCountIs(expected: Item, count: Int) {
|
||||||
|
val actualCount = getEntities(EntityType.ITEM).sumOf { if (it.item.`is`(expected)) it.item.count else 0 }
|
||||||
|
if (actualCount != count) {
|
||||||
|
throw GameTestAssertException("Expected $count ${expected.description.string} items to exist (found $actualCount)")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private fun getName(type: BlockEntityType<*>): ResourceLocation =
|
private fun getName(type: BlockEntityType<*>): ResourceLocation =
|
||||||
RegistryHelper.getKeyOrThrow(BuiltInRegistries.BLOCK_ENTITY_TYPE, type)
|
RegistryHelper.getKeyOrThrow(BuiltInRegistries.BLOCK_ENTITY_TYPE, type)
|
||||||
|
|
||||||
|
@@ -13,8 +13,14 @@ import dan200.computercraft.shared.computer.core.ServerContext
|
|||||||
import net.minecraft.core.BlockPos
|
import net.minecraft.core.BlockPos
|
||||||
import net.minecraft.gametest.framework.*
|
import net.minecraft.gametest.framework.*
|
||||||
import net.minecraft.server.MinecraftServer
|
import net.minecraft.server.MinecraftServer
|
||||||
|
import net.minecraft.server.level.ServerLevel
|
||||||
import net.minecraft.world.level.GameRules
|
import net.minecraft.world.level.GameRules
|
||||||
|
import net.minecraft.world.level.Level
|
||||||
|
import net.minecraft.world.level.LevelAccessor
|
||||||
|
import net.minecraft.world.level.block.Blocks
|
||||||
import net.minecraft.world.level.block.entity.StructureBlockEntity
|
import net.minecraft.world.level.block.entity.StructureBlockEntity
|
||||||
|
import net.minecraft.world.level.block.state.BlockState
|
||||||
|
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
|
||||||
@@ -188,4 +194,23 @@ object TestHooks {
|
|||||||
throw RuntimeException(e)
|
throw RuntimeException(e)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds a hook that makes breaking a bone block spawn an explosion.
|
||||||
|
*
|
||||||
|
* It would be more Correct to register a custom block, but that's quite a lot of work, and doesn't seem worth it
|
||||||
|
* for test code.
|
||||||
|
*
|
||||||
|
* See also [Turtle_Test.Breaks_exploding_block].
|
||||||
|
*/
|
||||||
|
@JvmStatic
|
||||||
|
fun onBeforeDestroyBlock(level: LevelAccessor, pos: BlockPos, state: BlockState): Boolean {
|
||||||
|
if (state.block === Blocks.BONE_BLOCK && level is ServerLevel) {
|
||||||
|
val explosionPos = Vec3.atCenterOf(pos)
|
||||||
|
level.explode(null, explosionPos.x, explosionPos.y, explosionPos.z, 4.0f, Level.ExplosionInteraction.TNT)
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
return false
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
138
projects/common/src/testMod/resources/data/cctest/structures/computer_test.drops_on_explosion.snbt
generated
Normal file
138
projects/common/src/testMod/resources/data/cctest/structures/computer_test.drops_on_explosion.snbt
generated
Normal file
@@ -0,0 +1,138 @@
|
|||||||
|
{
|
||||||
|
DataVersion: 3465,
|
||||||
|
size: [5, 5, 5],
|
||||||
|
data: [
|
||||||
|
{pos: [0, 0, 0], state: "minecraft:obsidian"},
|
||||||
|
{pos: [0, 0, 1], state: "minecraft:obsidian"},
|
||||||
|
{pos: [0, 0, 2], state: "minecraft:obsidian"},
|
||||||
|
{pos: [0, 0, 3], state: "minecraft:obsidian"},
|
||||||
|
{pos: [0, 0, 4], state: "minecraft:obsidian"},
|
||||||
|
{pos: [1, 0, 0], state: "minecraft:obsidian"},
|
||||||
|
{pos: [1, 0, 1], state: "minecraft:obsidian"},
|
||||||
|
{pos: [1, 0, 2], state: "minecraft:obsidian"},
|
||||||
|
{pos: [1, 0, 3], state: "minecraft:obsidian"},
|
||||||
|
{pos: [1, 0, 4], state: "minecraft:obsidian"},
|
||||||
|
{pos: [2, 0, 0], state: "minecraft:obsidian"},
|
||||||
|
{pos: [2, 0, 1], state: "minecraft:obsidian"},
|
||||||
|
{pos: [2, 0, 2], state: "minecraft:obsidian"},
|
||||||
|
{pos: [2, 0, 3], state: "minecraft:obsidian"},
|
||||||
|
{pos: [2, 0, 4], state: "minecraft:obsidian"},
|
||||||
|
{pos: [3, 0, 0], state: "minecraft:obsidian"},
|
||||||
|
{pos: [3, 0, 1], state: "minecraft:obsidian"},
|
||||||
|
{pos: [3, 0, 2], state: "minecraft:obsidian"},
|
||||||
|
{pos: [3, 0, 3], state: "minecraft:obsidian"},
|
||||||
|
{pos: [3, 0, 4], state: "minecraft:obsidian"},
|
||||||
|
{pos: [4, 0, 0], state: "minecraft:obsidian"},
|
||||||
|
{pos: [4, 0, 1], state: "minecraft:obsidian"},
|
||||||
|
{pos: [4, 0, 2], state: "minecraft:obsidian"},
|
||||||
|
{pos: [4, 0, 3], state: "minecraft:obsidian"},
|
||||||
|
{pos: [4, 0, 4], state: "minecraft:obsidian"},
|
||||||
|
{pos: [0, 1, 0], state: "minecraft:barrier"},
|
||||||
|
{pos: [0, 1, 1], state: "minecraft:barrier"},
|
||||||
|
{pos: [0, 1, 2], state: "minecraft:barrier"},
|
||||||
|
{pos: [0, 1, 3], state: "minecraft:barrier"},
|
||||||
|
{pos: [0, 1, 4], state: "minecraft:barrier"},
|
||||||
|
{pos: [1, 1, 0], state: "minecraft:barrier"},
|
||||||
|
{pos: [1, 1, 1], state: "minecraft:air"},
|
||||||
|
{pos: [1, 1, 2], state: "minecraft:air"},
|
||||||
|
{pos: [1, 1, 3], state: "minecraft:air"},
|
||||||
|
{pos: [1, 1, 4], state: "minecraft:barrier"},
|
||||||
|
{pos: [2, 1, 0], state: "minecraft:barrier"},
|
||||||
|
{pos: [2, 1, 1], state: "minecraft:air"},
|
||||||
|
{pos: [2, 1, 2], state: "computercraft:computer_normal{facing:east,state:off}", nbt: {On: 0b, id: "computercraft:computer_normal"}},
|
||||||
|
{pos: [2, 1, 3], state: "minecraft:air"},
|
||||||
|
{pos: [2, 1, 4], state: "minecraft:barrier"},
|
||||||
|
{pos: [3, 1, 0], state: "minecraft:barrier"},
|
||||||
|
{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:barrier"},
|
||||||
|
{pos: [4, 1, 0], state: "minecraft:barrier"},
|
||||||
|
{pos: [4, 1, 1], state: "minecraft:barrier"},
|
||||||
|
{pos: [4, 1, 2], state: "minecraft:barrier"},
|
||||||
|
{pos: [4, 1, 3], state: "minecraft:barrier"},
|
||||||
|
{pos: [4, 1, 4], state: "minecraft:barrier"},
|
||||||
|
{pos: [0, 2, 0], state: "minecraft:barrier"},
|
||||||
|
{pos: [0, 2, 1], state: "minecraft:barrier"},
|
||||||
|
{pos: [0, 2, 2], state: "minecraft:air"},
|
||||||
|
{pos: [0, 2, 3], state: "minecraft:barrier"},
|
||||||
|
{pos: [0, 2, 4], state: "minecraft:barrier"},
|
||||||
|
{pos: [1, 2, 0], state: "minecraft:barrier"},
|
||||||
|
{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:barrier"},
|
||||||
|
{pos: [2, 2, 0], state: "minecraft:barrier"},
|
||||||
|
{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:barrier"},
|
||||||
|
{pos: [3, 2, 0], state: "minecraft:barrier"},
|
||||||
|
{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:barrier"},
|
||||||
|
{pos: [4, 2, 0], state: "minecraft:barrier"},
|
||||||
|
{pos: [4, 2, 1], state: "minecraft:barrier"},
|
||||||
|
{pos: [4, 2, 2], state: "minecraft:barrier"},
|
||||||
|
{pos: [4, 2, 3], state: "minecraft:barrier"},
|
||||||
|
{pos: [4, 2, 4], state: "minecraft:barrier"},
|
||||||
|
{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:obsidian",
|
||||||
|
"minecraft:barrier",
|
||||||
|
"minecraft:air",
|
||||||
|
"computercraft:computer_normal{facing:east,state:off}"
|
||||||
|
]
|
||||||
|
}
|
137
projects/common/src/testMod/resources/data/cctest/structures/disk_drive_test.data_fixers.snbt
generated
Normal file
137
projects/common/src/testMod/resources/data/cctest/structures/disk_drive_test.data_fixers.snbt
generated
Normal file
@@ -0,0 +1,137 @@
|
|||||||
|
{
|
||||||
|
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:disk_drive{facing:north,state:full}", nbt: {Item: {Count: 1b, id: "computercraft:disk", tag: {Color: 123456, DiskId: 123}}, id: "computercraft:disk_drive"}},
|
||||||
|
{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: "minecraft:air"},
|
||||||
|
{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: "computercraft:disk_drive{facing:north,state:full}", nbt: {Item: {Count: 1b, id: "computercraft:treasure_disk", tag: {Colour: 123456, SubPath: "demo", Title: "Demo disk"}}, id: "computercraft:disk_drive"}},
|
||||||
|
{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:disk_drive{facing:north,state:full}"
|
||||||
|
]
|
||||||
|
}
|
137
projects/common/src/testMod/resources/data/cctest/structures/pocket_computer_test.data_fixers.snbt
generated
Normal file
137
projects/common/src/testMod/resources/data/cctest/structures/pocket_computer_test.data_fixers.snbt
generated
Normal file
@@ -0,0 +1,137 @@
|
|||||||
|
{
|
||||||
|
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: "minecraft:air"},
|
||||||
|
{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: "minecraft:chest{facing:north,type:single,waterlogged:false}", nbt: {Items: [{Count: 1b, Slot: 0b, id: "computercraft:pocket_computer_advanced", tag: {ComputerId: 123, Upgrade: "computercraft:wireless_modem_normal", display: {Name: '{"text":"Test"}'}}}], id: "minecraft:chest"}},
|
||||||
|
{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",
|
||||||
|
"minecraft:chest{facing:north,type:single,waterlogged:false}"
|
||||||
|
]
|
||||||
|
}
|
137
projects/common/src/testMod/resources/data/cctest/structures/printer_test.data_fixers.snbt
generated
Normal file
137
projects/common/src/testMod/resources/data/cctest/structures/printer_test.data_fixers.snbt
generated
Normal file
File diff suppressed because one or more lines are too long
139
projects/common/src/testMod/resources/data/cctest/structures/turtle_test.breaks_exploding_block.snbt
generated
Normal file
139
projects/common/src/testMod/resources/data/cctest/structures/turtle_test.breaks_exploding_block.snbt
generated
Normal file
@@ -0,0 +1,139 @@
|
|||||||
|
{
|
||||||
|
DataVersion: 3465,
|
||||||
|
size: [5, 5, 5],
|
||||||
|
data: [
|
||||||
|
{pos: [0, 0, 0], state: "minecraft:obsidian"},
|
||||||
|
{pos: [0, 0, 1], state: "minecraft:obsidian"},
|
||||||
|
{pos: [0, 0, 2], state: "minecraft:obsidian"},
|
||||||
|
{pos: [0, 0, 3], state: "minecraft:obsidian"},
|
||||||
|
{pos: [0, 0, 4], state: "minecraft:obsidian"},
|
||||||
|
{pos: [1, 0, 0], state: "minecraft:obsidian"},
|
||||||
|
{pos: [1, 0, 1], state: "minecraft:obsidian"},
|
||||||
|
{pos: [1, 0, 2], state: "minecraft:obsidian"},
|
||||||
|
{pos: [1, 0, 3], state: "minecraft:obsidian"},
|
||||||
|
{pos: [1, 0, 4], state: "minecraft:obsidian"},
|
||||||
|
{pos: [2, 0, 0], state: "minecraft:obsidian"},
|
||||||
|
{pos: [2, 0, 1], state: "minecraft:obsidian"},
|
||||||
|
{pos: [2, 0, 2], state: "minecraft:obsidian"},
|
||||||
|
{pos: [2, 0, 3], state: "minecraft:obsidian"},
|
||||||
|
{pos: [2, 0, 4], state: "minecraft:obsidian"},
|
||||||
|
{pos: [3, 0, 0], state: "minecraft:obsidian"},
|
||||||
|
{pos: [3, 0, 1], state: "minecraft:obsidian"},
|
||||||
|
{pos: [3, 0, 2], state: "minecraft:obsidian"},
|
||||||
|
{pos: [3, 0, 3], state: "minecraft:obsidian"},
|
||||||
|
{pos: [3, 0, 4], state: "minecraft:obsidian"},
|
||||||
|
{pos: [4, 0, 0], state: "minecraft:obsidian"},
|
||||||
|
{pos: [4, 0, 1], state: "minecraft:obsidian"},
|
||||||
|
{pos: [4, 0, 2], state: "minecraft:obsidian"},
|
||||||
|
{pos: [4, 0, 3], state: "minecraft:obsidian"},
|
||||||
|
{pos: [4, 0, 4], state: "minecraft:obsidian"},
|
||||||
|
{pos: [0, 1, 0], state: "minecraft:barrier"},
|
||||||
|
{pos: [0, 1, 1], state: "minecraft:barrier"},
|
||||||
|
{pos: [0, 1, 2], state: "minecraft:barrier"},
|
||||||
|
{pos: [0, 1, 3], state: "minecraft:barrier"},
|
||||||
|
{pos: [0, 1, 4], state: "minecraft:barrier"},
|
||||||
|
{pos: [1, 1, 0], state: "minecraft:barrier"},
|
||||||
|
{pos: [1, 1, 1], state: "minecraft:air"},
|
||||||
|
{pos: [1, 1, 2], state: "minecraft:air"},
|
||||||
|
{pos: [1, 1, 3], state: "minecraft:air"},
|
||||||
|
{pos: [1, 1, 4], state: "minecraft:barrier"},
|
||||||
|
{pos: [2, 1, 0], state: "minecraft:barrier"},
|
||||||
|
{pos: [2, 1, 1], state: "minecraft:air"},
|
||||||
|
{pos: [2, 1, 2], state: "computercraft:turtle_normal{facing:south,waterlogged:false}", nbt: {ComputerId: 1, Fuel: 0, Items: [], Label: "turtle_test.breaks_exploding_block", LeftUpgrade: "minecraft:diamond_pickaxe", LeftUpgradeNbt: {Tag: {Damage: 0}}, On: 1b, Owner: {LowerId: -5670393268852517359L, Name: "Player172", UpperId: 3578583684139923613L}, Slot: 0, Items: [{Count: 64b, Slot: 0b, id: "minecraft:bone_block"}], id: "computercraft:turtle_normal"}},
|
||||||
|
{pos: [2, 1, 3], state: "minecraft:bone_block{axis:y}"},
|
||||||
|
{pos: [2, 1, 4], state: "minecraft:barrier"},
|
||||||
|
{pos: [3, 1, 0], state: "minecraft:barrier"},
|
||||||
|
{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:barrier"},
|
||||||
|
{pos: [4, 1, 0], state: "minecraft:barrier"},
|
||||||
|
{pos: [4, 1, 1], state: "minecraft:barrier"},
|
||||||
|
{pos: [4, 1, 2], state: "minecraft:barrier"},
|
||||||
|
{pos: [4, 1, 3], state: "minecraft:barrier"},
|
||||||
|
{pos: [4, 1, 4], state: "minecraft:barrier"},
|
||||||
|
{pos: [0, 2, 0], state: "minecraft:barrier"},
|
||||||
|
{pos: [0, 2, 1], state: "minecraft:barrier"},
|
||||||
|
{pos: [0, 2, 2], state: "minecraft:air"},
|
||||||
|
{pos: [0, 2, 3], state: "minecraft:barrier"},
|
||||||
|
{pos: [0, 2, 4], state: "minecraft:barrier"},
|
||||||
|
{pos: [1, 2, 0], state: "minecraft:barrier"},
|
||||||
|
{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:barrier"},
|
||||||
|
{pos: [2, 2, 0], state: "minecraft:barrier"},
|
||||||
|
{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:barrier"},
|
||||||
|
{pos: [3, 2, 0], state: "minecraft:barrier"},
|
||||||
|
{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:barrier"},
|
||||||
|
{pos: [4, 2, 0], state: "minecraft:barrier"},
|
||||||
|
{pos: [4, 2, 1], state: "minecraft:barrier"},
|
||||||
|
{pos: [4, 2, 2], state: "minecraft:barrier"},
|
||||||
|
{pos: [4, 2, 3], state: "minecraft:barrier"},
|
||||||
|
{pos: [4, 2, 4], state: "minecraft:barrier"},
|
||||||
|
{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:obsidian",
|
||||||
|
"minecraft:barrier",
|
||||||
|
"minecraft:bone_block{axis:y}",
|
||||||
|
"minecraft:air",
|
||||||
|
"computercraft:turtle_normal{facing:south,waterlogged:false}"
|
||||||
|
]
|
||||||
|
}
|
@@ -8,9 +8,7 @@ import dan200.computercraft.api.peripheral.IComputerAccess;
|
|||||||
|
|
||||||
import javax.annotation.Nullable;
|
import javax.annotation.Nullable;
|
||||||
import java.nio.ByteBuffer;
|
import java.nio.ByteBuffer;
|
||||||
import java.util.Collection;
|
import java.util.*;
|
||||||
import java.util.Map;
|
|
||||||
import java.util.Objects;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The result of invoking a Lua method.
|
* The result of invoking a Lua method.
|
||||||
@@ -55,6 +53,12 @@ public final class MethodResult {
|
|||||||
* <p>
|
* <p>
|
||||||
* In order to provide a custom object with methods, one may return a {@link IDynamicLuaObject}, or an arbitrary
|
* In order to provide a custom object with methods, one may return a {@link IDynamicLuaObject}, or an arbitrary
|
||||||
* class with {@link LuaFunction} annotations. Anything else will be converted to {@code nil}.
|
* class with {@link LuaFunction} annotations. Anything else will be converted to {@code nil}.
|
||||||
|
* <p>
|
||||||
|
* Shared objects in a {@link MethodResult} will preserve their sharing when converted to Lua values. For instance,
|
||||||
|
* {@code Map<?, ?> m = new HashMap(); return MethodResult.of(m, m); } will return two values {@code a}, {@code b}
|
||||||
|
* where {@code a == b}. The one exception to this is Java's singleton collections ({@link List#of()},
|
||||||
|
* {@link Set#of()} and {@link Map#of()}), which are always converted to new table. This is not true for other
|
||||||
|
* singleton collections, such as those provided by {@link Collections} or Guava.
|
||||||
*
|
*
|
||||||
* @param value The value to return to the calling Lua function.
|
* @param value The value to return to the calling Lua function.
|
||||||
* @return A method result which returns immediately with the given value.
|
* @return A method result which returns immediately with the given value.
|
||||||
|
@@ -38,7 +38,7 @@ public interface IPeripheral {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Is called when when a computer is attaching to the peripheral.
|
* Is called when a computer is attaching to the peripheral.
|
||||||
* <p>
|
* <p>
|
||||||
* This will occur when a peripheral is placed next to an active computer, when a computer is turned on next to a
|
* This will occur when a peripheral is placed next to an active computer, when a computer is turned on next to a
|
||||||
* peripheral, when a turtle travels into a square next to a peripheral, or when a wired modem adjacent to this
|
* peripheral, when a turtle travels into a square next to a peripheral, or when a wired modem adjacent to this
|
||||||
|
@@ -101,7 +101,7 @@ public class FSAPI implements ILuaAPI {
|
|||||||
* }</pre>
|
* }</pre>
|
||||||
*/
|
*/
|
||||||
@LuaFunction
|
@LuaFunction
|
||||||
public final String[] list(String path) throws LuaException {
|
public final List<String> list(String path) throws LuaException {
|
||||||
try (var ignored = environment.time(Metrics.FS_OPS)) {
|
try (var ignored = environment.time(Metrics.FS_OPS)) {
|
||||||
return getFileSystem().list(path);
|
return getFileSystem().list(path);
|
||||||
} catch (FileSystemException e) {
|
} catch (FileSystemException e) {
|
||||||
|
@@ -122,7 +122,7 @@ public class OSAPI implements ILuaAPI {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private static long getEpochForCalendar(Calendar c) {
|
private static long getEpochForCalendar(Calendar c) {
|
||||||
return c.getTime().getTime();
|
return c.getTimeInMillis();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -298,7 +298,7 @@ public class OSAPI implements ILuaAPI {
|
|||||||
* textutils.formatTime(os.time())
|
* textutils.formatTime(os.time())
|
||||||
* }</pre>
|
* }</pre>
|
||||||
* @cc.since 1.2
|
* @cc.since 1.2
|
||||||
* @cc.changed 1.80pr1 Add support for getting the local local and UTC time.
|
* @cc.changed 1.80pr1 Add support for getting the local and UTC time.
|
||||||
* @cc.changed 1.82.0 Arguments are now case insensitive.
|
* @cc.changed 1.82.0 Arguments are now case insensitive.
|
||||||
* @cc.changed 1.83.0 {@link #time(IArguments)} now accepts table arguments and converts them to UNIX timestamps.
|
* @cc.changed 1.83.0 {@link #time(IArguments)} now accepts table arguments and converts them to UNIX timestamps.
|
||||||
* @see #date To get a date table that can be converted with this function.
|
* @see #date To get a date table that can be converted with this function.
|
||||||
|
@@ -177,9 +177,9 @@ public abstract class AbstractHandle {
|
|||||||
/**
|
/**
|
||||||
* Read the remainder of the file.
|
* Read the remainder of the file.
|
||||||
*
|
*
|
||||||
* @return The file, or {@code null} if at the end of it.
|
* @return The remaining contents of the file, or {@code null} in the event of an error.
|
||||||
* @throws LuaException If the file has been closed.
|
* @throws LuaException If the file has been closed.
|
||||||
* @cc.treturn string|nil The remaining contents of the file, or {@code nil} if we are at the end.
|
* @cc.treturn string|nil The remaining contents of the file, or {@code nil} in the event of an error.
|
||||||
* @cc.since 1.80pr1
|
* @cc.since 1.80pr1
|
||||||
*/
|
*/
|
||||||
@Nullable
|
@Nullable
|
||||||
|
@@ -57,7 +57,7 @@ interface AddressPredicate {
|
|||||||
prefixSize = Integer.parseInt(prefixSizeStr);
|
prefixSize = Integer.parseInt(prefixSizeStr);
|
||||||
} catch (NumberFormatException e) {
|
} catch (NumberFormatException e) {
|
||||||
throw new InvalidRuleException(String.format(
|
throw new InvalidRuleException(String.format(
|
||||||
"Invalid host host '%s': Cannot extract size of CIDR mask from '%s'.",
|
"Invalid host '%s': Cannot extract size of CIDR mask from '%s'.",
|
||||||
addressStr + '/' + prefixSizeStr, prefixSizeStr
|
addressStr + '/' + prefixSizeStr, prefixSizeStr
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
@@ -270,7 +270,7 @@ final class Generator<T> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Fold over the original method's arguments, excluding the target in reverse. For each argument, we reduce
|
// Fold over the original method's arguments, excluding the target in reverse. For each argument, we reduce
|
||||||
// a method of type type (target, args..., arg_n, context..., IArguments) -> _ to (target, args..., context..., IArguments) -> _
|
// a method of type (target, args..., arg_n, context..., IArguments) -> _ to (target, args..., context..., IArguments) -> _
|
||||||
// until eventually we've flattened the whole list.
|
// until eventually we've flattened the whole list.
|
||||||
for (var i = parameterTypes.size() - 1; i >= 0; i--) {
|
for (var i = parameterTypes.size() - 1; i >= 0; i--) {
|
||||||
handle = MethodHandles.foldArguments(handle, i + 1, argSelectors.get(i));
|
handle = MethodHandles.foldArguments(handle, i + 1, argSelectors.get(i));
|
||||||
|
@@ -122,6 +122,10 @@ public class FileSystem {
|
|||||||
}
|
}
|
||||||
|
|
||||||
var lastSlash = path.lastIndexOf('/');
|
var lastSlash = path.lastIndexOf('/');
|
||||||
|
|
||||||
|
// If the trailing segment is a "..", then just append another one.
|
||||||
|
if (path.substring(lastSlash < 0 ? 0 : lastSlash + 1).equals("..")) return path + "/..";
|
||||||
|
|
||||||
if (lastSlash >= 0) {
|
if (lastSlash >= 0) {
|
||||||
return path.substring(0, lastSlash);
|
return path.substring(0, lastSlash);
|
||||||
} else {
|
} else {
|
||||||
@@ -145,7 +149,7 @@ public class FileSystem {
|
|||||||
return getMount(sanitizePath(path)).getAttributes(sanitizePath(path));
|
return getMount(sanitizePath(path)).getAttributes(sanitizePath(path));
|
||||||
}
|
}
|
||||||
|
|
||||||
public synchronized String[] list(String path) throws FileSystemException {
|
public synchronized List<String> list(String path) throws FileSystemException {
|
||||||
path = sanitizePath(path);
|
path = sanitizePath(path);
|
||||||
var mount = getMount(path);
|
var mount = getMount(path);
|
||||||
|
|
||||||
@@ -161,10 +165,8 @@ public class FileSystem {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Return list
|
// Return list
|
||||||
var array = new String[list.size()];
|
list.sort(Comparator.naturalOrder());
|
||||||
list.toArray(array);
|
return list;
|
||||||
Arrays.sort(array);
|
|
||||||
return array;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public synchronized boolean exists(String path) throws FileSystemException {
|
public synchronized boolean exists(String path) throws FileSystemException {
|
||||||
|
@@ -13,6 +13,7 @@ import dan200.computercraft.core.Logging;
|
|||||||
import dan200.computercraft.core.computer.TimeoutState;
|
import dan200.computercraft.core.computer.TimeoutState;
|
||||||
import dan200.computercraft.core.methods.LuaMethod;
|
import dan200.computercraft.core.methods.LuaMethod;
|
||||||
import dan200.computercraft.core.methods.MethodSupplier;
|
import dan200.computercraft.core.methods.MethodSupplier;
|
||||||
|
import dan200.computercraft.core.util.LuaUtil;
|
||||||
import dan200.computercraft.core.util.Nullability;
|
import dan200.computercraft.core.util.Nullability;
|
||||||
import dan200.computercraft.core.util.SanitisedError;
|
import dan200.computercraft.core.util.SanitisedError;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
@@ -183,10 +184,35 @@ public class CobaltLuaMachine implements ILuaMachine {
|
|||||||
return ValueFactory.valueOf(bytes);
|
return ValueFactory.valueOf(bytes);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Don't share singleton values, and instead convert them to a new table.
|
||||||
|
if (LuaUtil.isSingletonCollection(object)) return new LuaTable();
|
||||||
|
|
||||||
if (values == null) values = new IdentityHashMap<>(1);
|
if (values == null) values = new IdentityHashMap<>(1);
|
||||||
var result = values.get(object);
|
var result = values.get(object);
|
||||||
if (result != null) return result;
|
if (result != null) return result;
|
||||||
|
|
||||||
|
var wrapped = toValueWorker(object, values);
|
||||||
|
if (wrapped == null) {
|
||||||
|
LOG.warn(Logging.JAVA_ERROR, "Received unknown type '{}', returning nil.", object.getClass().getName());
|
||||||
|
return Constants.NIL;
|
||||||
|
}
|
||||||
|
|
||||||
|
values.put(object, wrapped);
|
||||||
|
return wrapped;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Convert a complex Java object (such as a collection or Lua object) to a Lua value.
|
||||||
|
* <p>
|
||||||
|
* This is a worker function for {@link #toValue(Object, IdentityHashMap)}, which handles the actual construction
|
||||||
|
* of values, without reading/writing from the value map.
|
||||||
|
*
|
||||||
|
* @param object The object to convert.
|
||||||
|
* @param values The map of Java to Lua values.
|
||||||
|
* @return The converted value, or {@code null} if it could not be converted.
|
||||||
|
* @throws LuaError If the value could not be converted.
|
||||||
|
*/
|
||||||
|
private @Nullable LuaValue toValueWorker(Object object, IdentityHashMap<Object, LuaValue> values) throws LuaError {
|
||||||
if (object instanceof ILuaFunction) {
|
if (object instanceof ILuaFunction) {
|
||||||
return new ResultInterpreterFunction(this, FUNCTION_METHOD, object, context, object.toString());
|
return new ResultInterpreterFunction(this, FUNCTION_METHOD, object, context, object.toString());
|
||||||
}
|
}
|
||||||
@@ -194,15 +220,12 @@ public class CobaltLuaMachine implements ILuaMachine {
|
|||||||
if (object instanceof IDynamicLuaObject) {
|
if (object instanceof IDynamicLuaObject) {
|
||||||
LuaValue wrapped = wrapLuaObject(object);
|
LuaValue wrapped = wrapLuaObject(object);
|
||||||
if (wrapped == null) wrapped = new LuaTable();
|
if (wrapped == null) wrapped = new LuaTable();
|
||||||
values.put(object, wrapped);
|
|
||||||
return wrapped;
|
return wrapped;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (object instanceof Map<?, ?> map) {
|
if (object instanceof Map<?, ?> map) {
|
||||||
var table = new LuaTable();
|
var table = new LuaTable();
|
||||||
values.put(object, table);
|
for (var pair : map.entrySet()) {
|
||||||
|
|
||||||
for (Map.Entry<?, ?> pair : map.entrySet()) {
|
|
||||||
var key = toValue(pair.getKey(), values);
|
var key = toValue(pair.getKey(), values);
|
||||||
var value = toValue(pair.getValue(), values);
|
var value = toValue(pair.getValue(), values);
|
||||||
if (!key.isNil() && !value.isNil()) table.rawset(key, value);
|
if (!key.isNil() && !value.isNil()) table.rawset(key, value);
|
||||||
@@ -212,27 +235,18 @@ public class CobaltLuaMachine implements ILuaMachine {
|
|||||||
|
|
||||||
if (object instanceof Collection<?> objects) {
|
if (object instanceof Collection<?> objects) {
|
||||||
var table = new LuaTable(objects.size(), 0);
|
var table = new LuaTable(objects.size(), 0);
|
||||||
values.put(object, table);
|
|
||||||
var i = 0;
|
var i = 0;
|
||||||
for (Object child : objects) table.rawset(++i, toValue(child, values));
|
for (var child : objects) table.rawset(++i, toValue(child, values));
|
||||||
return table;
|
return table;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (object instanceof Object[] objects) {
|
if (object instanceof Object[] objects) {
|
||||||
var table = new LuaTable(objects.length, 0);
|
var table = new LuaTable(objects.length, 0);
|
||||||
values.put(object, table);
|
|
||||||
for (var i = 0; i < objects.length; i++) table.rawset(i + 1, toValue(objects[i], values));
|
for (var i = 0; i < objects.length; i++) table.rawset(i + 1, toValue(objects[i], values));
|
||||||
return table;
|
return table;
|
||||||
}
|
}
|
||||||
|
|
||||||
var wrapped = wrapLuaObject(object);
|
return wrapLuaObject(object);
|
||||||
if (wrapped != null) {
|
|
||||||
values.put(object, wrapped);
|
|
||||||
return wrapped;
|
|
||||||
}
|
|
||||||
|
|
||||||
LOG.warn(Logging.JAVA_ERROR, "Received unknown type '{}', returning nil.", object.getClass().getName());
|
|
||||||
return Constants.NIL;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Varargs toValues(@Nullable Object[] objects) throws LuaError {
|
Varargs toValues(@Nullable Object[] objects) throws LuaError {
|
||||||
|
@@ -4,9 +4,18 @@
|
|||||||
|
|
||||||
package dan200.computercraft.core.util;
|
package dan200.computercraft.core.util;
|
||||||
|
|
||||||
|
import dan200.computercraft.core.lua.ILuaMachine;
|
||||||
|
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
public class LuaUtil {
|
public class LuaUtil {
|
||||||
|
private static final List<?> EMPTY_LIST = List.of();
|
||||||
|
private static final Set<?> EMPTY_SET = Set.of();
|
||||||
|
private static final Map<?, ?> EMPTY_MAP = Map.of();
|
||||||
|
|
||||||
public static Object[] consArray(Object value, Collection<?> rest) {
|
public static Object[] consArray(Object value, Collection<?> rest) {
|
||||||
if (rest.isEmpty()) return new Object[]{ value };
|
if (rest.isEmpty()) return new Object[]{ value };
|
||||||
|
|
||||||
@@ -14,7 +23,20 @@ public class LuaUtil {
|
|||||||
var out = new Object[rest.size() + 1];
|
var out = new Object[rest.size() + 1];
|
||||||
out[0] = value;
|
out[0] = value;
|
||||||
var i = 1;
|
var i = 1;
|
||||||
for (Object additionalType : rest) out[i++] = additionalType;
|
for (var additionalType : rest) out[i++] = additionalType;
|
||||||
return out;
|
return out;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Determine whether a value is a singleton collection, such as one created with {@link List#of()}.
|
||||||
|
* <p>
|
||||||
|
* These collections are treated specially by {@link ILuaMachine} implementations: we skip sharing for them, and
|
||||||
|
* create a new table each time.
|
||||||
|
*
|
||||||
|
* @param value The value to test.
|
||||||
|
* @return Whether this is a singleton collection.
|
||||||
|
*/
|
||||||
|
public static boolean isSingletonCollection(Object value) {
|
||||||
|
return value == EMPTY_LIST || value == EMPTY_SET || value == EMPTY_MAP;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@@ -851,13 +851,32 @@ unserialise = unserialize -- GB version
|
|||||||
|
|
||||||
--[[- Returns a JSON representation of the given data.
|
--[[- Returns a JSON representation of the given data.
|
||||||
|
|
||||||
This function attempts to guess whether a table is a JSON array or
|
|
||||||
object. However, empty tables are assumed to be empty objects - use
|
|
||||||
[`textutils.empty_json_array`] to mark an empty array.
|
|
||||||
|
|
||||||
This is largely intended for interacting with various functions from the
|
This is largely intended for interacting with various functions from the
|
||||||
[`commands`] API, though may also be used in making [`http`] requests.
|
[`commands`] API, though may also be used in making [`http`] requests.
|
||||||
|
|
||||||
|
Lua has a rather different data model to Javascript/JSON. As a result, some Lua
|
||||||
|
values do not serialise cleanly into JSON.
|
||||||
|
|
||||||
|
- Lua tables can contain arbitrary key-value pairs, but JSON only accepts arrays,
|
||||||
|
and objects (which require a string key). When serialising a table, if it only
|
||||||
|
has numeric keys, then it will be treated as an array. Otherwise, the table will
|
||||||
|
be serialised to an object using the string keys. Non-string keys (such as numbers
|
||||||
|
or tables) will be dropped.
|
||||||
|
|
||||||
|
A consequence of this is that an empty table will always be serialised to an object,
|
||||||
|
not an array. [`textutils.empty_json_array`] may be used to express an empty array.
|
||||||
|
|
||||||
|
- Lua strings are an a sequence of raw bytes, and do not have any specific encoding.
|
||||||
|
However, JSON strings must be valid unicode. By default, non-ASCII characters in a
|
||||||
|
string are serialised to their unicode code point (for instance, `"\xfe"` is
|
||||||
|
converted to `"\u00fe"`). The `unicode_strings` option may be set to treat all input
|
||||||
|
strings as UTF-8.
|
||||||
|
|
||||||
|
- Lua does not distinguish between missing keys (`undefined` in JS) and ones explicitly
|
||||||
|
set to `null`. As a result `{ x = nil }` is serialised to `{}`. [`textutils.json_null`]
|
||||||
|
may be used to get an explicit null value (`{ x = textutils.json_null }` will serialise
|
||||||
|
to `{"x": null}`).
|
||||||
|
|
||||||
@param[1] t The value to serialise. Like [`textutils.serialise`], this should not
|
@param[1] t The value to serialise. Like [`textutils.serialise`], this should not
|
||||||
contain recursive tables or functions.
|
contain recursive tables or functions.
|
||||||
@tparam[1,opt] {
|
@tparam[1,opt] {
|
||||||
|
@@ -114,7 +114,7 @@ local vector = {
|
|||||||
--
|
--
|
||||||
-- @tparam Vector self The first vector to compute the dot product of.
|
-- @tparam Vector self The first vector to compute the dot product of.
|
||||||
-- @tparam Vector o The second vector to compute the dot product of.
|
-- @tparam Vector o The second vector to compute the dot product of.
|
||||||
-- @treturn Vector The dot product of `self` and `o`.
|
-- @treturn number The dot product of `self` and `o`.
|
||||||
-- @usage v1:dot(v2)
|
-- @usage v1:dot(v2)
|
||||||
dot = function(self, o)
|
dot = function(self, o)
|
||||||
if getmetatable(self) ~= vmetatable then expect(1, self, "vector") end
|
if getmetatable(self) ~= vmetatable then expect(1, self, "vector") end
|
||||||
|
@@ -1,3 +1,15 @@
|
|||||||
|
# New features in CC: Tweaked 1.113.0
|
||||||
|
|
||||||
|
* Allow placing printed pages and books in lecterns.
|
||||||
|
|
||||||
|
Several bug fixes:
|
||||||
|
* Various documentation fixes (MCJack123)
|
||||||
|
* Fix computers and turtles not being dropped when exploded with TNT.
|
||||||
|
* Fix crash when turtles are broken while mining a block.
|
||||||
|
* Fix pocket computer terminals not updating when in the off-hand.
|
||||||
|
* Fix disk drives not being exposed as a peripheral.
|
||||||
|
* Fix item details being non-serialisable due to duplicated tables.
|
||||||
|
|
||||||
# New features in CC: Tweaked 1.112.0
|
# New features in CC: Tweaked 1.112.0
|
||||||
|
|
||||||
* Report a custom error when using `!` instead of `not`.
|
* Report a custom error when using `!` instead of `not`.
|
||||||
@@ -811,7 +823,7 @@ And several bug fixes:
|
|||||||
# New features in CC: Tweaked 1.86.2
|
# New features in CC: Tweaked 1.86.2
|
||||||
|
|
||||||
* Fix peripheral.getMethods returning an empty table.
|
* Fix peripheral.getMethods returning an empty table.
|
||||||
* Update to Minecraft 1.15.2. This is currently alpha-quality and so is missing missing features and may be unstable.
|
* Update to Minecraft 1.15.2. This is currently alpha-quality and so is missing features and may be unstable.
|
||||||
|
|
||||||
# New features in CC: Tweaked 1.86.1
|
# New features in CC: Tweaked 1.86.1
|
||||||
|
|
||||||
@@ -1427,7 +1439,7 @@ And several bug fixes:
|
|||||||
* Turtles can now compare items in their inventories
|
* Turtles can now compare items in their inventories
|
||||||
* Turtles can place signs with text on them with `turtle.place( [signText] )`
|
* Turtles can place signs with text on them with `turtle.place( [signText] )`
|
||||||
* Turtles now optionally require fuel items to move, and can refuel themselves
|
* Turtles now optionally require fuel items to move, and can refuel themselves
|
||||||
* The size of the the turtle inventory has been increased to 16
|
* The size of the turtle inventory has been increased to 16
|
||||||
* The size of the turtle screen has been increased
|
* The size of the turtle screen has been increased
|
||||||
* New turtle functions: `turtle.compareTo( [slotNum] )`, `turtle.craft()`, `turtle.attack()`, `turtle.attackUp()`, `turtle.attackDown()`, `turtle.dropUp()`, `turtle.dropDown()`, `turtle.getFuelLevel()`, `turtle.refuel()`
|
* New turtle functions: `turtle.compareTo( [slotNum] )`, `turtle.craft()`, `turtle.attack()`, `turtle.attackUp()`, `turtle.attackDown()`, `turtle.dropUp()`, `turtle.dropDown()`, `turtle.getFuelLevel()`, `turtle.refuel()`
|
||||||
* New disk function: disk.getID()
|
* New disk function: disk.getID()
|
||||||
|
@@ -1,14 +1,13 @@
|
|||||||
New features in CC: Tweaked 1.112.0
|
New features in CC: Tweaked 1.113.0
|
||||||
|
|
||||||
* Report a custom error when using `!` instead of `not`.
|
* Allow placing printed pages and books in lecterns.
|
||||||
* Update several translations (zyxkad, MineKID-LP).
|
|
||||||
* Add `cc.strings.split` function.
|
|
||||||
|
|
||||||
Several bug fixes:
|
Several bug fixes:
|
||||||
* Fix `drive.getAudioTitle` returning `nil` when no disk is inserted.
|
* Various documentation fixes (MCJack123)
|
||||||
* Preserve item data when upgrading pocket computers.
|
* Fix computers and turtles not being dropped when exploded with TNT.
|
||||||
* Add missing bounds check to `cc.strings.wrap` (Lupus950).
|
* Fix crash when turtles are broken while mining a block.
|
||||||
* Fix dyed turtles rendering transparent.
|
* Fix pocket computer terminals not updating when in the off-hand.
|
||||||
* Fix dupe bug when crafting with turtles.
|
* Fix disk drives not being exposed as a peripheral.
|
||||||
|
* Fix item details being non-serialisable due to duplicated tables.
|
||||||
|
|
||||||
Type "help changelog" to see the full version history.
|
Type "help changelog" to see the full version history.
|
||||||
|
@@ -13,7 +13,7 @@ Typically DFPWM audio is read from [the filesystem][`fs.ReadHandle`] or a [a web
|
|||||||
and converted a format suitable for [`speaker.playAudio`].
|
and converted a format suitable for [`speaker.playAudio`].
|
||||||
|
|
||||||
## Encoding and decoding files
|
## Encoding and decoding files
|
||||||
This modules exposes two key functions, [`make_decoder`] and [`make_encoder`], which construct a new decoder or encoder.
|
This module exposes two key functions, [`make_decoder`] and [`make_encoder`], which construct a new decoder or encoder.
|
||||||
The returned encoder/decoder is itself a function, which converts between the two kinds of data.
|
The returned encoder/decoder is itself a function, which converts between the two kinds of data.
|
||||||
|
|
||||||
These encoders and decoders have lots of hidden state, so you should be careful to use the same encoder or decoder for
|
These encoders and decoders have lots of hidden state, so you should be careful to use the same encoder or decoder for
|
||||||
@@ -21,9 +21,9 @@ a specific audio stream. Typically you will want to create a decoder for each st
|
|||||||
for each one you write.
|
for each one you write.
|
||||||
|
|
||||||
## Converting audio to DFPWM
|
## Converting audio to DFPWM
|
||||||
DFPWM is not a popular file format and so standard audio processing tools will not have an option to export to it.
|
DFPWM is not a popular file format and so standard audio processing tools may not have an option to export to it.
|
||||||
Instead, you can convert audio files online using [music.madefor.cc], the [LionRay Wav Converter][LionRay] Java
|
Instead, you can convert audio files online using [music.madefor.cc], the [LionRay Wav Converter][LionRay] Java
|
||||||
application or development builds of [FFmpeg].
|
application or [FFmpeg] 5.1 or later.
|
||||||
|
|
||||||
[music.madefor.cc]: https://music.madefor.cc/ "DFPWM audio converter for Computronics and CC: Tweaked"
|
[music.madefor.cc]: https://music.madefor.cc/ "DFPWM audio converter for Computronics and CC: Tweaked"
|
||||||
[LionRay]: https://github.com/gamax92/LionRay/ "LionRay Wav Converter "
|
[LionRay]: https://github.com/gamax92/LionRay/ "LionRay Wav Converter "
|
||||||
@@ -211,7 +211,7 @@ end
|
|||||||
|
|
||||||
--[[- A convenience function for encoding a complete file of audio at once.
|
--[[- A convenience function for encoding a complete file of audio at once.
|
||||||
|
|
||||||
This should only be used for complete pieces of audio. If you are writing writing multiple chunks to the same place,
|
This should only be used for complete pieces of audio. If you are writing multiple chunks to the same place,
|
||||||
you should use an encoder returned by [`make_encoder`] instead.
|
you should use an encoder returned by [`make_encoder`] instead.
|
||||||
|
|
||||||
@tparam { number... } input The table of amplitude data.
|
@tparam { number... } input The table of amplitude data.
|
||||||
|
@@ -5,11 +5,14 @@
|
|||||||
package dan200.computercraft.core.computer;
|
package dan200.computercraft.core.computer;
|
||||||
|
|
||||||
import com.google.common.io.CharStreams;
|
import com.google.common.io.CharStreams;
|
||||||
|
import dan200.computercraft.api.lua.ILuaAPI;
|
||||||
|
import dan200.computercraft.api.lua.LuaFunction;
|
||||||
import org.junit.jupiter.api.Assertions;
|
import org.junit.jupiter.api.Assertions;
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
import java.io.InputStreamReader;
|
import java.io.InputStreamReader;
|
||||||
import java.nio.charset.StandardCharsets;
|
import java.nio.charset.StandardCharsets;
|
||||||
|
import java.util.List;
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
|
|
||||||
import static java.time.Duration.ofSeconds;
|
import static java.time.Duration.ofSeconds;
|
||||||
@@ -30,6 +33,26 @@ public class ComputerTest {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testDuplicateObjects() {
|
||||||
|
class CustomApi implements ILuaAPI {
|
||||||
|
@Override
|
||||||
|
public String[] getNames() {
|
||||||
|
return new String[]{ "custom" };
|
||||||
|
}
|
||||||
|
|
||||||
|
@LuaFunction
|
||||||
|
public final Object[] getObjects() {
|
||||||
|
return new Object[]{ List.of(), List.of() };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ComputerBootstrap.run("""
|
||||||
|
local x, y = custom.getObjects()
|
||||||
|
assert(x ~= y)
|
||||||
|
""", i -> i.addApi(new CustomApi()), 50);
|
||||||
|
}
|
||||||
|
|
||||||
public static void main(String[] args) throws Exception {
|
public static void main(String[] args) throws Exception {
|
||||||
var stream = ComputerTest.class.getClassLoader().getResourceAsStream("benchmark.lua");
|
var stream = ComputerTest.class.getClassLoader().getResourceAsStream("benchmark.lua");
|
||||||
try (var reader = new InputStreamReader(Objects.requireNonNull(stream), StandardCharsets.UTF_8)) {
|
try (var reader = new InputStreamReader(Objects.requireNonNull(stream), StandardCharsets.UTF_8)) {
|
||||||
|
@@ -158,6 +158,65 @@ describe("The fs library", function()
|
|||||||
expect(fs.combine("", "a")):eq("a")
|
expect(fs.combine("", "a")):eq("a")
|
||||||
expect(fs.combine("a", "", "b", "c")):eq("a/b/c")
|
expect(fs.combine("a", "", "b", "c")):eq("a/b/c")
|
||||||
end)
|
end)
|
||||||
|
|
||||||
|
it("preserves pattern characters", function()
|
||||||
|
expect(fs.combine("foo*?")):eq("foo*?")
|
||||||
|
end)
|
||||||
|
|
||||||
|
it("sanitises paths", function()
|
||||||
|
expect(fs.combine("foo\":<>|")):eq("foo")
|
||||||
|
end)
|
||||||
|
end)
|
||||||
|
|
||||||
|
describe("fs.getName", function()
|
||||||
|
it("returns 'root' for the empty path", function()
|
||||||
|
expect(fs.getName("")):eq("root")
|
||||||
|
expect(fs.getName("foo/..")):eq("root")
|
||||||
|
end)
|
||||||
|
|
||||||
|
it("returns the file name", function()
|
||||||
|
expect(fs.getName("foo/bar")):eq("bar")
|
||||||
|
expect(fs.getName("foo/bar/")):eq("bar")
|
||||||
|
expect(fs.getName("../foo")):eq("foo")
|
||||||
|
end)
|
||||||
|
|
||||||
|
it("returns '..' for parent directories", function()
|
||||||
|
expect(fs.getName("..")):eq("..")
|
||||||
|
end)
|
||||||
|
|
||||||
|
it("preserves pattern characters", function()
|
||||||
|
expect(fs.getName("foo*?")):eq("foo*?")
|
||||||
|
end)
|
||||||
|
|
||||||
|
it("sanitises paths", function()
|
||||||
|
expect(fs.getName("foo\":<>|")):eq("foo")
|
||||||
|
end)
|
||||||
|
end)
|
||||||
|
|
||||||
|
describe("fs.getDir", function()
|
||||||
|
it("returns '..' for the empty path", function()
|
||||||
|
expect(fs.getDir("")):eq("..")
|
||||||
|
expect(fs.getDir("foo/..")):eq("..")
|
||||||
|
end)
|
||||||
|
|
||||||
|
it("returns the directory name", function()
|
||||||
|
expect(fs.getDir("foo/bar")):eq("foo")
|
||||||
|
expect(fs.getDir("foo/bar/")):eq("foo")
|
||||||
|
expect(fs.getDir("../foo")):eq("..")
|
||||||
|
end)
|
||||||
|
|
||||||
|
it("returns '..' for parent directories", function()
|
||||||
|
expect(fs.getDir("..")):eq("../..")
|
||||||
|
expect(fs.getDir("../..")):eq("../../..")
|
||||||
|
end)
|
||||||
|
|
||||||
|
it("preserves pattern characters", function()
|
||||||
|
expect(fs.getDir("foo*?/x")):eq("foo*?")
|
||||||
|
end)
|
||||||
|
|
||||||
|
it("sanitises paths", function()
|
||||||
|
expect(fs.getDir("foo\":<>|/x")):eq("foo")
|
||||||
|
end)
|
||||||
end)
|
end)
|
||||||
|
|
||||||
describe("fs.getSize", function()
|
describe("fs.getSize", function()
|
||||||
@@ -200,6 +259,14 @@ describe("The fs library", function()
|
|||||||
handle.close()
|
handle.close()
|
||||||
end)
|
end)
|
||||||
|
|
||||||
|
it("reading an empty file returns nil", function()
|
||||||
|
local file = create_test_file ""
|
||||||
|
|
||||||
|
local handle = fs.open(file, mode)
|
||||||
|
expect(handle.read()):eq(nil)
|
||||||
|
handle.close()
|
||||||
|
end)
|
||||||
|
|
||||||
it("can read a line of text", function()
|
it("can read a line of text", function()
|
||||||
local file = create_test_file "some\nfile\r\ncontents\n\n"
|
local file = create_test_file "some\nfile\r\ncontents\n\n"
|
||||||
|
|
||||||
@@ -223,6 +290,16 @@ describe("The fs library", function()
|
|||||||
expect(handle.readLine(true)):eq(nil)
|
expect(handle.readLine(true)):eq(nil)
|
||||||
handle.close()
|
handle.close()
|
||||||
end)
|
end)
|
||||||
|
|
||||||
|
it("readAll always returns a string", function()
|
||||||
|
local contents = "some\nfile\ncontents"
|
||||||
|
local file = create_test_file "some\nfile\ncontents"
|
||||||
|
|
||||||
|
local handle = fs.open(file, mode)
|
||||||
|
expect(handle.readAll()):eq(contents)
|
||||||
|
expect(handle.readAll()):eq("")
|
||||||
|
handle.close()
|
||||||
|
end)
|
||||||
end
|
end
|
||||||
|
|
||||||
describe("reading", function()
|
describe("reading", function()
|
||||||
|
@@ -188,4 +188,31 @@ describe("The os library", function()
|
|||||||
expect.error(os.loadAPI, nil):eq("bad argument #1 (string expected, got nil)")
|
expect.error(os.loadAPI, nil):eq("bad argument #1 (string expected, got nil)")
|
||||||
end)
|
end)
|
||||||
end)
|
end)
|
||||||
|
|
||||||
|
describe("os.queueEvent", function()
|
||||||
|
local function roundtrip(...)
|
||||||
|
local event_name = ("event_%08x"):format(math.random(1, 0x7FFFFFFF))
|
||||||
|
os.queueEvent(event_name, ...)
|
||||||
|
return select(2, os.pullEvent(event_name))
|
||||||
|
end
|
||||||
|
|
||||||
|
it("preserves references in tables", function()
|
||||||
|
local tbl = {}
|
||||||
|
local xs = roundtrip({ tbl, tbl })
|
||||||
|
expect(xs[1]):eq(xs[2])
|
||||||
|
end)
|
||||||
|
|
||||||
|
it("does not preserve references in separate args", function()
|
||||||
|
-- I'm not sure I like this behaviour, but it is what CC has always done.
|
||||||
|
local tbl = {}
|
||||||
|
local xs, ys = roundtrip(tbl, tbl)
|
||||||
|
expect(xs):ne(ys)
|
||||||
|
end)
|
||||||
|
|
||||||
|
it("clones objects", function()
|
||||||
|
local tbl = {}
|
||||||
|
local xs = roundtrip(tbl)
|
||||||
|
expect(xs):ne(tbl)
|
||||||
|
end)
|
||||||
|
end)
|
||||||
end)
|
end)
|
||||||
|
@@ -13,13 +13,13 @@ correct tokens and positions, and that it can report sensible error messages.
|
|||||||
We can lex some basic comments:
|
We can lex some basic comments:
|
||||||
|
|
||||||
```lua
|
```lua
|
||||||
-- A basic singleline comment comment
|
-- A basic singleline comment
|
||||||
--[ Not a multiline comment
|
--[ Not a multiline comment
|
||||||
--[= Also not a multiline comment!
|
--[= Also not a multiline comment!
|
||||||
```
|
```
|
||||||
|
|
||||||
```txt
|
```txt
|
||||||
1:1-1:37 COMMENT -- A basic singleline comment comment
|
1:1-1:29 COMMENT -- A basic singleline comment
|
||||||
2:1-2:27 COMMENT --[ Not a multiline comment
|
2:1-2:27 COMMENT --[ Not a multiline comment
|
||||||
3:1-3:34 COMMENT --[= Also not a multiline comment!
|
3:1-3:34 COMMENT --[= Also not a multiline comment!
|
||||||
```
|
```
|
||||||
|
@@ -122,6 +122,7 @@ public class ComputerCraft {
|
|||||||
|
|
||||||
PlayerBlockBreakEvents.BEFORE.register(FabricCommonHooks::onBlockDestroy);
|
PlayerBlockBreakEvents.BEFORE.register(FabricCommonHooks::onBlockDestroy);
|
||||||
UseBlockCallback.EVENT.register(FabricCommonHooks::useOnBlock);
|
UseBlockCallback.EVENT.register(FabricCommonHooks::useOnBlock);
|
||||||
|
UseBlockCallback.EVENT.register(CommonHooks::onUseBlock);
|
||||||
|
|
||||||
LootTableEvents.MODIFY.register((id, tableBuilder, source, registries) -> {
|
LootTableEvents.MODIFY.register((id, tableBuilder, source, registries) -> {
|
||||||
var pool = CommonHooks.getExtraLootPool(id);
|
var pool = CommonHooks.getExtraLootPool(id);
|
||||||
|
@@ -46,8 +46,8 @@
|
|||||||
],
|
],
|
||||||
"depends": {
|
"depends": {
|
||||||
"fabricloader": ">=0.15.10",
|
"fabricloader": ">=0.15.10",
|
||||||
"fabric-api": ">=0.100.5",
|
"fabric-api": ">=0.102.1",
|
||||||
"minecraft": "=1.21"
|
"minecraft": "=1.21.1"
|
||||||
},
|
},
|
||||||
"accessWidener": "computercraft.accesswidener"
|
"accessWidener": "computercraft.accesswidener"
|
||||||
}
|
}
|
||||||
|
@@ -14,6 +14,7 @@ import net.fabricmc.fabric.api.command.v2.CommandRegistrationCallback;
|
|||||||
import net.fabricmc.fabric.api.event.Event;
|
import net.fabricmc.fabric.api.event.Event;
|
||||||
import net.fabricmc.fabric.api.event.lifecycle.v1.ServerLifecycleEvents;
|
import net.fabricmc.fabric.api.event.lifecycle.v1.ServerLifecycleEvents;
|
||||||
import net.fabricmc.fabric.api.event.lifecycle.v1.ServerTickEvents;
|
import net.fabricmc.fabric.api.event.lifecycle.v1.ServerTickEvents;
|
||||||
|
import net.fabricmc.fabric.api.event.player.PlayerBlockBreakEvents;
|
||||||
import net.minecraft.gametest.framework.GameTestRegistry;
|
import net.minecraft.gametest.framework.GameTestRegistry;
|
||||||
import net.minecraft.resources.ResourceLocation;
|
import net.minecraft.resources.ResourceLocation;
|
||||||
|
|
||||||
@@ -26,6 +27,7 @@ public class TestMod implements ModInitializer, ClientModInitializer {
|
|||||||
ServerLifecycleEvents.SERVER_STARTED.addPhaseOrdering(Event.DEFAULT_PHASE, phase);
|
ServerLifecycleEvents.SERVER_STARTED.addPhaseOrdering(Event.DEFAULT_PHASE, phase);
|
||||||
ServerLifecycleEvents.SERVER_STARTED.register(phase, TestHooks::onServerStarted);
|
ServerLifecycleEvents.SERVER_STARTED.register(phase, TestHooks::onServerStarted);
|
||||||
CommandRegistrationCallback.EVENT.register((dispatcher, registryAccess, environment) -> CCTestCommand.register(dispatcher));
|
CommandRegistrationCallback.EVENT.register((dispatcher, registryAccess, environment) -> CCTestCommand.register(dispatcher));
|
||||||
|
PlayerBlockBreakEvents.BEFORE.register((level, player, pos, state, blockEntity) -> !TestHooks.onBeforeDestroyBlock(level, pos, state));
|
||||||
|
|
||||||
TestHooks.loadTests(GameTestRegistry::register);
|
TestHooks.loadTests(GameTestRegistry::register);
|
||||||
}
|
}
|
||||||
|
@@ -9,6 +9,7 @@ import dan200.computercraft.shared.command.CommandComputerCraft;
|
|||||||
import net.minecraft.core.registries.Registries;
|
import net.minecraft.core.registries.Registries;
|
||||||
import net.minecraft.resources.ResourceKey;
|
import net.minecraft.resources.ResourceKey;
|
||||||
import net.minecraft.server.level.ServerLevel;
|
import net.minecraft.server.level.ServerLevel;
|
||||||
|
import net.minecraft.world.InteractionResult;
|
||||||
import net.minecraft.world.level.chunk.LevelChunk;
|
import net.minecraft.world.level.chunk.LevelChunk;
|
||||||
import net.neoforged.bus.api.EventPriority;
|
import net.neoforged.bus.api.EventPriority;
|
||||||
import net.neoforged.bus.api.SubscribeEvent;
|
import net.neoforged.bus.api.SubscribeEvent;
|
||||||
@@ -18,6 +19,7 @@ import net.neoforged.neoforge.event.LootTableLoadEvent;
|
|||||||
import net.neoforged.neoforge.event.RegisterCommandsEvent;
|
import net.neoforged.neoforge.event.RegisterCommandsEvent;
|
||||||
import net.neoforged.neoforge.event.entity.EntityJoinLevelEvent;
|
import net.neoforged.neoforge.event.entity.EntityJoinLevelEvent;
|
||||||
import net.neoforged.neoforge.event.entity.living.LivingDropsEvent;
|
import net.neoforged.neoforge.event.entity.living.LivingDropsEvent;
|
||||||
|
import net.neoforged.neoforge.event.entity.player.PlayerInteractEvent;
|
||||||
import net.neoforged.neoforge.event.level.ChunkEvent;
|
import net.neoforged.neoforge.event.level.ChunkEvent;
|
||||||
import net.neoforged.neoforge.event.level.ChunkTicketLevelUpdatedEvent;
|
import net.neoforged.neoforge.event.level.ChunkTicketLevelUpdatedEvent;
|
||||||
import net.neoforged.neoforge.event.level.ChunkWatchEvent;
|
import net.neoforged.neoforge.event.level.ChunkWatchEvent;
|
||||||
@@ -78,6 +80,15 @@ public class ForgeCommonHooks {
|
|||||||
CommonHooks.onChunkTicketLevelChanged(event.getLevel(), event.getChunkPos(), event.getOldTicketLevel(), event.getNewTicketLevel());
|
CommonHooks.onChunkTicketLevelChanged(event.getLevel(), event.getChunkPos(), event.getOldTicketLevel(), event.getNewTicketLevel());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@SubscribeEvent
|
||||||
|
public static void onUseBlock(PlayerInteractEvent.RightClickBlock event) {
|
||||||
|
var result = CommonHooks.onUseBlock(event.getEntity(), event.getLevel(), event.getHand(), event.getHitVec());
|
||||||
|
if (result == InteractionResult.PASS) return;
|
||||||
|
|
||||||
|
event.setCanceled(true);
|
||||||
|
event.setCancellationResult(result);
|
||||||
|
}
|
||||||
|
|
||||||
@SubscribeEvent
|
@SubscribeEvent
|
||||||
public static void onAddReloadListeners(AddReloadListenerEvent event) {
|
public static void onAddReloadListeners(AddReloadListenerEvent event) {
|
||||||
CommonHooks.onDatapackReload((id, listener) -> event.addListener(listener));
|
CommonHooks.onDatapackReload((id, listener) -> event.addListener(listener));
|
||||||
|
@@ -26,7 +26,7 @@ CC: Tweaked is a fork of ComputerCraft, adding programmable computers, turtles a
|
|||||||
[[dependencies.computercraft]]
|
[[dependencies.computercraft]]
|
||||||
modId="neoforge"
|
modId="neoforge"
|
||||||
type="required"
|
type="required"
|
||||||
versionRange="[${neoVersion},21.1)"
|
versionRange="[${neoVersion},21.2)"
|
||||||
ordering="NONE"
|
ordering="NONE"
|
||||||
side="BOTH"
|
side="BOTH"
|
||||||
|
|
||||||
|
@@ -15,6 +15,7 @@ import net.neoforged.neoforge.client.event.ScreenEvent;
|
|||||||
import net.neoforged.neoforge.common.NeoForge;
|
import net.neoforged.neoforge.common.NeoForge;
|
||||||
import net.neoforged.neoforge.event.RegisterCommandsEvent;
|
import net.neoforged.neoforge.event.RegisterCommandsEvent;
|
||||||
import net.neoforged.neoforge.event.RegisterGameTestsEvent;
|
import net.neoforged.neoforge.event.RegisterGameTestsEvent;
|
||||||
|
import net.neoforged.neoforge.event.level.BlockEvent;
|
||||||
import net.neoforged.neoforge.event.server.ServerStartedEvent;
|
import net.neoforged.neoforge.event.server.ServerStartedEvent;
|
||||||
import net.neoforged.neoforge.event.tick.ServerTickEvent;
|
import net.neoforged.neoforge.event.tick.ServerTickEvent;
|
||||||
|
|
||||||
@@ -26,6 +27,10 @@ public class TestMod {
|
|||||||
var bus = NeoForge.EVENT_BUS;
|
var bus = NeoForge.EVENT_BUS;
|
||||||
bus.addListener(EventPriority.LOW, (ServerStartedEvent e) -> TestHooks.onServerStarted(e.getServer()));
|
bus.addListener(EventPriority.LOW, (ServerStartedEvent e) -> TestHooks.onServerStarted(e.getServer()));
|
||||||
bus.addListener((RegisterCommandsEvent e) -> CCTestCommand.register(e.getDispatcher()));
|
bus.addListener((RegisterCommandsEvent e) -> CCTestCommand.register(e.getDispatcher()));
|
||||||
|
bus.addListener((BlockEvent.BreakEvent e) -> {
|
||||||
|
if (TestHooks.onBeforeDestroyBlock(e.getLevel(), e.getPos(), e.getState())) e.setCanceled(true);
|
||||||
|
});
|
||||||
|
|
||||||
if (FMLEnvironment.dist == Dist.CLIENT) TestMod.onInitializeClient();
|
if (FMLEnvironment.dist == Dist.CLIENT) TestMod.onInitializeClient();
|
||||||
|
|
||||||
modBus.addListener((RegisterGameTestsEvent event) -> TestHooks.loadTests(event::register));
|
modBus.addListener((RegisterGameTestsEvent event) -> TestHooks.loadTests(event::register));
|
||||||
|
@@ -0,0 +1,75 @@
|
|||||||
|
// SPDX-FileCopyrightText: 2024 The CC: Tweaked Developers
|
||||||
|
//
|
||||||
|
// SPDX-License-Identifier: MPL-2.0
|
||||||
|
|
||||||
|
@file:Suppress("JAVA_MODULE_DOES_NOT_EXPORT_PACKAGE")
|
||||||
|
|
||||||
|
package cc.tweaked.linter
|
||||||
|
|
||||||
|
import com.google.errorprone.BugPattern
|
||||||
|
import com.google.errorprone.VisitorState
|
||||||
|
import com.google.errorprone.bugpatterns.BugChecker
|
||||||
|
import com.google.errorprone.matchers.Description
|
||||||
|
import com.google.errorprone.util.ASTHelpers
|
||||||
|
import com.sun.source.tree.*
|
||||||
|
import com.sun.source.util.TreeScanner
|
||||||
|
import com.sun.tools.javac.code.Symbol.MethodSymbol
|
||||||
|
import javax.lang.model.element.Modifier
|
||||||
|
|
||||||
|
@BugPattern(
|
||||||
|
summary = "Checks that a methods invoke their super method.",
|
||||||
|
explanation = """
|
||||||
|
This extends ErrorProne's built in "MustCallSuper" with several additional Minecraft-specific methods.
|
||||||
|
""",
|
||||||
|
severity = BugPattern.SeverityLevel.ERROR,
|
||||||
|
tags = [BugPattern.StandardTags.LIKELY_ERROR],
|
||||||
|
)
|
||||||
|
class ExtraMustCallSuper : BugChecker(), BugChecker.MethodTreeMatcher {
|
||||||
|
companion object {
|
||||||
|
private val REQUIRED_METHODS = setOf(
|
||||||
|
MethodReference("net.minecraft.world.level.block.entity.BlockEntity", "setRemoved"),
|
||||||
|
MethodReference("net.minecraft.world.level.block.entity.BlockEntity", "clearRemoved"),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun matchMethod(tree: MethodTree, state: VisitorState): Description {
|
||||||
|
val methodSym: MethodSymbol = ASTHelpers.getSymbol(tree)
|
||||||
|
if (methodSym.modifiers.contains(Modifier.ABSTRACT)) return Description.NO_MATCH
|
||||||
|
|
||||||
|
val superMethod: MethodReference = findRequiredSuper(methodSym, state) ?: return Description.NO_MATCH
|
||||||
|
val foundSuper = SuperScanner(superMethod.method).scan(tree, Unit) ?: false
|
||||||
|
if (foundSuper) return Description.NO_MATCH
|
||||||
|
|
||||||
|
return buildDescription(tree)
|
||||||
|
.setMessage("This method overrides %s#%s but does not call the super method.".format(superMethod.owner, superMethod.method))
|
||||||
|
.build()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun findRequiredSuper(method: MethodSymbol, state: VisitorState): MethodReference? {
|
||||||
|
for (superMethod in ASTHelpers.findSuperMethods(method, state.types)) {
|
||||||
|
val superName = MethodReference(superMethod.owner.qualifiedName.toString(), superMethod.name.toString())
|
||||||
|
if (REQUIRED_METHODS.contains(superName)) return superName
|
||||||
|
}
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
private data class MethodReference(val owner: String, val method: String)
|
||||||
|
|
||||||
|
private class SuperScanner(private val methodName: String) : TreeScanner<Boolean?, Unit>() {
|
||||||
|
// Skip visiting other elements.
|
||||||
|
override fun visitClass(tree: ClassTree, state: Unit): Boolean = false
|
||||||
|
override fun visitLambdaExpression(tree: LambdaExpressionTree, state: Unit): Boolean = false
|
||||||
|
|
||||||
|
override fun visitMethodInvocation(tree: MethodInvocationTree, state: Unit): Boolean? {
|
||||||
|
val methodSelect: ExpressionTree = tree.methodSelect
|
||||||
|
if (methodSelect.kind == Tree.Kind.MEMBER_SELECT) {
|
||||||
|
val memberSelect = methodSelect as MemberSelectTree
|
||||||
|
if (ASTHelpers.isSuper(memberSelect.expression) && memberSelect.identifier.contentEquals(methodName)) return true
|
||||||
|
}
|
||||||
|
|
||||||
|
return super.visitMethodInvocation(tree, state)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun reduce(r1: Boolean?, r2: Boolean?): Boolean = (r1 ?: false) || (r2 ?: false)
|
||||||
|
}
|
||||||
|
}
|
@@ -1,6 +1,7 @@
|
|||||||
# SPDX-FileCopyrightText: 2022 The CC: Tweaked Developers
|
# SPDX-FileCopyrightText: 2022 The CC: Tweaked Developers
|
||||||
#
|
#
|
||||||
# SPDX-License-Identifier: MPL-2.0
|
# SPDX-License-Identifier: MPL-2.0
|
||||||
|
cc.tweaked.linter.ExtraMustCallSuper
|
||||||
cc.tweaked.linter.LoaderOverride
|
cc.tweaked.linter.LoaderOverride
|
||||||
cc.tweaked.linter.MissingLoaderOverride
|
cc.tweaked.linter.MissingLoaderOverride
|
||||||
cc.tweaked.linter.SideChecker
|
cc.tweaked.linter.SideChecker
|
||||||
|
Reference in New Issue
Block a user