1
0
mirror of https://github.com/SquidDev-CC/CC-Tweaked synced 2025-04-21 10:13:19 +00:00

Merge branch 'mc-1.20.x' into mc-1.21.x

This commit is contained in:
Jonathan Coates 2025-03-09 12:35:44 +00:00
commit 8f4d4038f6
No known key found for this signature in database
GPG Key ID: B9E431FF07C98D06
22 changed files with 577 additions and 255 deletions

View File

@ -1,16 +0,0 @@
// SPDX-FileCopyrightText: 2022 The CC: Tweaked Developers
//
// SPDX-License-Identifier: MPL-2.0
package cc.tweaked.gradle
import groovy.util.Node
import groovy.util.NodeList
object XmlUtil {
fun findChild(node: Node, name: String): Node? = when (val child = node.get(name)) {
is Node -> child
is NodeList -> child.singleOrNull() as Node?
else -> null
}
}

View File

@ -58,11 +58,11 @@ junitPlatform = "1.11.4"
jmh = "1.37"
# Build tools
cctJavadoc = "1.8.3"
cctJavadoc = "1.8.4"
checkstyle = "10.21.2"
errorProne-core = "2.36.0"
errorProne-plugin = "4.1.0"
fabric-loom = "1.9.2"
fabric-loom = "1.10.3"
githubRelease = "2.5.2"
gradleVersions = "0.50.0"
ideaExt = "1.1.7"
@ -75,7 +75,7 @@ shadow = "8.3.1"
spotless = "6.23.3"
taskTree = "2.1.1"
teavm = "0.11.0-SQUID.1"
vanillaExtract = "0.2.0"
vanillaExtract = "0.2.1"
versionCatalogUpdate = "0.8.1"
[libraries]

View File

@ -51,10 +51,10 @@ public abstract class ComponentDetailProvider<T> implements DetailProvider<DataC
* 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.
* @param data The full details to be returned for this item stack. New properties should be added to this map.
* @param component The component to provide details for.
*/
public abstract void provideComponentDetails(Map<? super String, Object> data, T item);
public abstract void provideComponentDetails(Map<? super String, Object> data, T component);
@Override
public final void provideDetails(Map<? super String, Object> data, DataComponentHolder holder) {

View File

@ -52,6 +52,8 @@ import net.minecraft.world.item.ItemStack;
import net.minecraft.world.item.component.DyedItemColor;
import net.minecraft.world.level.ItemLike;
import org.jspecify.annotations.Nullable;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.File;
import java.io.IOException;
@ -69,6 +71,8 @@ import java.util.function.Supplier;
* @see ModRegistry The common registry for actual game objects.
*/
public final class ClientRegistry {
private static final Logger LOG = LoggerFactory.getLogger(ClientRegistry.class);
private ClientRegistry() {
}
@ -192,7 +196,18 @@ public final class ClientRegistry {
}
public static void registerShaders(ResourceProvider resources, BiConsumer<ShaderInstance, Consumer<ShaderInstance>> load) throws IOException {
RenderTypes.registerShaders(resources, load);
RenderTypes.registerShaders(resources, (name, create, onLoaded) -> {
ShaderInstance shader;
try {
shader = create.get();
} catch (Exception e) {
LOG.error("Failed to load {}", name, e);
onLoaded.accept(null);
return;
}
load.accept(shader, onLoaded);
});
}
private record UnclampedPropertyFunction(

View File

@ -12,6 +12,7 @@ import dan200.computercraft.client.pocket.ClientPocketComputers;
import dan200.computercraft.client.render.text.FixedWidthFontRenderer;
import dan200.computercraft.core.terminal.Terminal;
import dan200.computercraft.core.util.Colour;
import dan200.computercraft.shared.ModRegistry;
import dan200.computercraft.shared.lectern.CustomLecternBlockEntity;
import dan200.computercraft.shared.media.items.PrintoutData;
import dan200.computercraft.shared.media.items.PrintoutItem;
@ -59,9 +60,9 @@ public class CustomLecternRenderer implements BlockEntityRenderer<CustomLecternB
poseStack.translate(0, -0.125f, 0);
var item = lectern.getItem();
if (item.getItem() instanceof PrintoutItem printout) {
if (item.getItem() instanceof PrintoutItem) {
var vertexConsumer = LecternPrintoutModel.MATERIAL.buffer(buffer, RenderType::entitySolid);
if (printout.getType() == PrintoutItem.Type.BOOK) {
if (item.is(ModRegistry.Items.PRINTED_BOOK.get())) {
printoutModel.renderBook(poseStack, vertexConsumer, packedLight, packedOverlay);
} else {
printoutModel.renderPages(poseStack, vertexConsumer, packedLight, packedOverlay, PrintoutData.getOrEmpty(item).pages());

View File

@ -8,7 +8,6 @@ import com.mojang.blaze3d.vertex.PoseStack;
import com.mojang.math.Axis;
import dan200.computercraft.shared.ModRegistry;
import dan200.computercraft.shared.media.items.PrintoutData;
import dan200.computercraft.shared.media.items.PrintoutItem;
import net.minecraft.client.renderer.MultiBufferSource;
import net.minecraft.world.entity.EntityType;
import net.minecraft.world.entity.decoration.ItemFrame;
@ -53,7 +52,7 @@ public final class PrintoutItemRenderer extends ItemMapLikeRenderer {
var pageData = stack.getOrDefault(ModRegistry.DataComponents.PRINTOUT.get(), PrintoutData.EMPTY);
var pages = pageData.pages();
var book = ((PrintoutItem) stack.getItem()).getType() == PrintoutItem.Type.BOOK;
var book = stack.is(ModRegistry.Items.PRINTED_BOOK.get());
double width = LINE_LENGTH * FONT_WIDTH + X_TEXT_MARGIN * 2;
double height = LINES_PER_PAGE * FONT_HEIGHT + Y_TEXT_MARGIN * 2;

View File

@ -16,11 +16,11 @@ import net.minecraft.client.renderer.RenderType;
import net.minecraft.client.renderer.ShaderInstance;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.server.packs.resources.ResourceProvider;
import org.apache.commons.io.function.IOSupplier;
import org.jspecify.annotations.Nullable;
import java.io.IOException;
import java.util.Objects;
import java.util.function.BiConsumer;
import java.util.function.Consumer;
/**
@ -68,9 +68,12 @@ public class RenderTypes {
return Objects.requireNonNull(GameRenderer.getRendertypeTextShader(), "Text shader has not been registered");
}
public static void registerShaders(ResourceProvider resources, BiConsumer<ShaderInstance, Consumer<ShaderInstance>> load) throws IOException {
load.accept(
new MonitorTextureBufferShader(
public interface ShaderLoader {
void tryLoad(String name, IOSupplier<ShaderInstance> create, Consumer<@Nullable ShaderInstance> accept) throws IOException;
}
public static void registerShaders(ResourceProvider resources, ShaderLoader load) throws IOException {
load.tryLoad("monitor shader", () -> new MonitorTextureBufferShader(
resources,
ComputerCraftAPI.MOD_ID + "/monitor_tbo",
MONITOR_TBO.format()

View File

@ -284,11 +284,11 @@ public final class ModRegistry {
}
public static final RegistryEntry<PrintoutItem> PRINTED_PAGE = REGISTRY.register("printed_page",
() -> new PrintoutItem(printoutProperties(), PrintoutItem.Type.PAGE));
() -> new PrintoutItem(printoutProperties()));
public static final RegistryEntry<PrintoutItem> PRINTED_PAGES = REGISTRY.register("printed_pages",
() -> new PrintoutItem(printoutProperties(), PrintoutItem.Type.PAGES));
() -> new PrintoutItem(printoutProperties()));
public static final RegistryEntry<PrintoutItem> PRINTED_BOOK = REGISTRY.register("printed_book",
() -> new PrintoutItem(printoutProperties(), PrintoutItem.Type.BOOK));
() -> new PrintoutItem(printoutProperties()));
public static final RegistryEntry<BlockItem> SPEAKER = ofBlock(Blocks.SPEAKER, BlockItem::new);
public static final RegistryEntry<BlockItem> DISK_DRIVE = ofBlock(Blocks.DISK_DRIVE, BlockItem::new);

View File

@ -20,17 +20,8 @@ import net.minecraft.world.level.Level;
import java.util.List;
public class PrintoutItem extends Item {
public enum Type {
PAGE,
PAGES,
BOOK
}
private final Type type;
public PrintoutItem(Properties settings, Type type) {
public PrintoutItem(Properties settings) {
super(settings);
this.type = type;
}
@Override
@ -49,8 +40,4 @@ public class PrintoutItem extends Item {
}
return new InteractionResultHolder<>(InteractionResult.sidedSuccess(world.isClientSide), stack);
}
public Type getType() {
return type;
}
}

View File

@ -170,8 +170,7 @@ public final class PrinterBlockEntity extends AbstractContainerBlockEntity imple
}
static boolean isPaper(ItemStack stack) {
var item = stack.getItem();
return item == Items.PAPER || item == ModRegistry.Items.PRINTED_PAGE.get();
return stack.is(Items.PAPER) || stack.is(ModRegistry.Items.PRINTED_PAGE.get());
}
private boolean canInputPage() {

View File

@ -13,7 +13,9 @@ import dan200.computercraft.core.metrics.Metrics;
import dan200.computercraft.core.metrics.MetricsObserver;
import dan200.computercraft.shared.peripheral.generic.methods.AbstractInventoryMethods;
import dan200.computercraft.shared.turtle.core.*;
import org.jspecify.annotations.Nullable;
import java.util.Map;
import java.util.Optional;
/**
@ -659,6 +661,7 @@ public class TurtleAPI implements ILuaAPI {
* @cc.treturn [2] string The reason equipping this item failed.
* @cc.since 1.6
* @see #equipRight()
* @see #getEquippedLeft()
*/
@LuaFunction
public final MethodResult equipLeft() {
@ -678,12 +681,45 @@ public class TurtleAPI implements ILuaAPI {
* @cc.treturn [2] string The reason equipping this item failed.
* @cc.since 1.6
* @see #equipLeft()
* @see #getEquippedRight()
*/
@LuaFunction
public final MethodResult equipRight() {
return trackCommand(new TurtleEquipCommand(TurtleSide.RIGHT));
}
/**
* Get the upgrade currently equipped on the left of the turtle.
* <p>
* This returns information about the currently equipped item, in the same form as
* {@link #getItemDetail(ILuaContext, Optional, Optional)}.
*
* @return Details about the currently equipped item, or {@code nil} if no upgrade is equipped.
* @see #equipLeft()
* @cc.since 1.116.0
*/
@LuaFunction(mainThread = true)
public final @Nullable Map<?, ?> getEquippedLeft() {
var upgrade = turtle.getUpgradeWithData(TurtleSide.LEFT);
return upgrade == null ? null : VanillaDetailRegistries.ITEM_STACK.getDetails(upgrade.getUpgradeItem());
}
/**
* Get the upgrade currently equipped on the right of the turtle.
* <p>
* This returns information about the currently equipped item, in the same form as
* {@link #getItemDetail(ILuaContext, Optional, Optional)}.
*
* @return Details about the currently equipped item, or {@code nil} if no upgrade is equipped.
* @see #equipRight()
* @cc.since 1.116.0
*/
@LuaFunction(mainThread = true)
public final @Nullable Map<?, ?> getEquippedRight() {
var upgrade = turtle.getUpgradeWithData(TurtleSide.RIGHT);
return upgrade == null ? null : VanillaDetailRegistries.ITEM_STACK.getDetails(upgrade.getUpgradeItem());
}
/**
* Get information about the block in front of the turtle.
*

View File

@ -1,7 +1,7 @@
{
"parent": "computercraft:block/turtle_base",
"textures": {
"texture": "computercraft:block/turtle_colour",
"particle": "computercraft:block/turtle_colour_body_front",
"body_back": "computercraft:block/turtle_colour_body_back",
"body_backpack": "computercraft:block/turtle_colour_body_backpack",
"body_bottom": "computercraft:block/turtle_colour_body_bottom",

View File

@ -27,6 +27,11 @@ public class ItemStackMatcher extends TypeSafeMatcher<ItemStack> {
description.appendValue(stack).appendValue(stack.getComponentsPatch());
}
@Override
protected void describeMismatchSafely(ItemStack item, Description description) {
description.appendText("was ").appendValue(stack).appendValue(stack.getComponentsPatch());
}
public static Matcher<ItemStack> isStack(ItemStack stack) {
return new ItemStackMatcher(stack);
}

View File

@ -5,9 +5,20 @@
package dan200.computercraft.gametest
import dan200.computercraft.gametest.api.*
import dan200.computercraft.shared.ModRegistry
import dan200.computercraft.shared.media.items.PrintoutData
import dan200.computercraft.shared.util.DataComponentUtil
import dan200.computercraft.test.shared.ItemStackMatcher.isStack
import net.minecraft.gametest.framework.GameTest
import net.minecraft.gametest.framework.GameTestGenerator
import net.minecraft.gametest.framework.GameTestHelper
import net.minecraft.gametest.framework.TestFunction
import net.minecraft.world.item.Item
import net.minecraft.world.item.ItemStack
import net.minecraft.world.item.Items
import org.hamcrest.MatcherAssert.assertThat
import org.junit.jupiter.api.Assertions.assertEquals
import java.util.function.Supplier
class Printout_Test {
/**
@ -56,4 +67,54 @@ class Printout_Test {
thenExecute { helper.level.dayTime = Times.NOON }
}
@GameTest(template = "default")
fun Craft_pages(helper: GameTestHelper) = helper.immediate {
// Assert that crafting with only one page fails
helper.assertNotCraftable(ItemStack(ModRegistry.Items.PRINTED_PAGE.get()), ItemStack(Items.STRING))
// Assert that crafting with no pages fails
helper.assertNotCraftable(ItemStack(Items.PAPER), ItemStack(Items.PAPER), ItemStack(Items.STRING))
// Assert that crafting with a book fails
helper.assertNotCraftable(ItemStack(ModRegistry.Items.PRINTED_PAGE.get()), ItemStack(ModRegistry.Items.PRINTED_BOOK.get()), ItemStack(Items.STRING))
assertThat(
helper.craftItem(
createPrintoutOf(ModRegistry.Items.PRINTED_PAGE, "First"),
createPrintoutOf(ModRegistry.Items.PRINTED_PAGES, "First"),
ItemStack(Items.STRING),
),
isStack(createPrintoutOf(ModRegistry.Items.PRINTED_PAGES, "First", 2)),
)
}
@GameTest(template = "default")
fun Craft_book(helper: GameTestHelper) = helper.immediate {
// Assert that crafting with no pages fails
helper.assertNotCraftable(ItemStack(Items.PAPER), ItemStack(Items.PAPER), ItemStack(Items.STRING), ItemStack(Items.LEATHER))
// Assert that crafting with only one page works
assertEquals(
ModRegistry.Items.PRINTED_BOOK.get(),
helper.craftItem(ItemStack(ModRegistry.Items.PRINTED_PAGE.get()), ItemStack(Items.STRING), ItemStack(Items.LEATHER)).item,
)
assertThat(
helper.craftItem(
createPrintoutOf(ModRegistry.Items.PRINTED_PAGE, "First"),
createPrintoutOf(ModRegistry.Items.PRINTED_PAGES, "First"),
ItemStack(Items.STRING),
ItemStack(Items.LEATHER),
),
isStack(createPrintoutOf(ModRegistry.Items.PRINTED_BOOK, "First", 2)),
)
}
private fun createPrintoutOf(item: Supplier<out Item>, title: String, pages: Int = 1): ItemStack {
val line =
PrintoutData.Line(" ".repeat(PrintoutData.LINE_LENGTH), 'b'.toString().repeat(PrintoutData.LINE_LENGTH))
val lines = List(PrintoutData.LINES_PER_PAGE * pages) { line }
return DataComponentUtil.createStack(item.get(), ModRegistry.DataComponents.PRINTOUT.get(), PrintoutData(title, lines))
}
}

View File

@ -6,19 +6,16 @@ package dan200.computercraft.gametest
import com.mojang.authlib.GameProfile
import dan200.computercraft.gametest.api.Structures
import dan200.computercraft.gametest.api.craftItem
import dan200.computercraft.gametest.api.sequence
import dan200.computercraft.shared.ModRegistry
import net.minecraft.core.NonNullList
import net.minecraft.core.component.DataComponentPatch
import net.minecraft.core.component.DataComponents
import net.minecraft.gametest.framework.GameTest
import net.minecraft.gametest.framework.GameTestAssertException
import net.minecraft.gametest.framework.GameTestHelper
import net.minecraft.world.item.ItemStack
import net.minecraft.world.item.Items
import net.minecraft.world.item.component.ResolvableProfile
import net.minecraft.world.item.crafting.CraftingInput
import net.minecraft.world.item.crafting.RecipeType
import org.junit.jupiter.api.Assertions.assertEquals
import java.util.*
@ -31,16 +28,10 @@ class Recipe_Test {
@GameTest(template = Structures.DEFAULT)
fun Craft_result_has_nbt(context: GameTestHelper) = context.sequence {
thenExecute {
val items = NonNullList.withSize(3 * 3, ItemStack.EMPTY)
items[0] = ItemStack(Items.SKELETON_SKULL)
items[1] = ItemStack(ModRegistry.Items.COMPUTER_ADVANCED.get())
val container = CraftingInput.of(3, 3, items)
val recipe = context.level.server.recipeManager
.getRecipeFor(RecipeType.CRAFTING, container, context.level)
.orElseThrow { GameTestAssertException("No recipe matches") }
val result = recipe.value.assemble(container, context.level.registryAccess())
val result = context.craftItem(
ItemStack(Items.SKELETON_SKULL),
ItemStack(ModRegistry.Items.COMPUTER_ADVANCED.get()),
)
val profile = GameProfile(UUID.fromString("f3c8d69b-0776-4512-8434-d1b2165909eb"), "dan200")

View File

@ -6,7 +6,7 @@ package dan200.computercraft.gametest
import dan200.computercraft.api.ComputerCraftAPI
import dan200.computercraft.api.ComputerCraftTags
import dan200.computercraft.api.detail.BasicItemDetailProvider
import dan200.computercraft.api.detail.ComponentDetailProvider
import dan200.computercraft.api.detail.VanillaDetailRegistries
import dan200.computercraft.api.lua.ObjectArguments
import dan200.computercraft.api.turtle.ITurtleUpgrade
@ -18,7 +18,7 @@ import dan200.computercraft.gametest.core.TestHooks
import dan200.computercraft.mixin.gametest.GameTestHelperAccessor
import dan200.computercraft.mixin.gametest.GameTestInfoAccessor
import dan200.computercraft.shared.ModRegistry
import dan200.computercraft.shared.media.items.PrintoutItem
import dan200.computercraft.shared.media.items.PrintoutData
import dan200.computercraft.shared.peripheral.modem.wired.CableBlock
import dan200.computercraft.shared.peripheral.modem.wired.CableModemVariant
import dan200.computercraft.shared.peripheral.monitor.MonitorBlock
@ -453,16 +453,16 @@ class Turtle_Test {
thenExecute {
VanillaDetailRegistries.ITEM_STACK.addProvider(
object :
BasicItemDetailProvider<PrintoutItem>("printout", PrintoutItem::class.java) {
override fun provideDetails(data: MutableMap<in String, Any>, stack: ItemStack, item: PrintoutItem) {
data["type"] = item.type.toString().lowercase()
ComponentDetailProvider<PrintoutData>("printout", ModRegistry.DataComponents.PRINTOUT.get()) {
override fun provideComponentDetails(data: MutableMap<in String, Any>, component: PrintoutData) {
data["pages"] = component.pages()
}
},
)
}
thenOnComputer {
val details = getTurtleItemDetail(detailed = true)
assertEquals(mapOf("type" to "page"), details["printout"]) {
assertEquals(mapOf("pages" to 1), details["printout"]) {
"Printout information is returned (whole map is $details)"
}
}

View File

@ -15,6 +15,7 @@ import dan200.computercraft.test.shared.ItemStackMatcher.isStack
import net.minecraft.commands.arguments.blocks.BlockInput
import net.minecraft.core.BlockPos
import net.minecraft.core.Direction
import net.minecraft.core.NonNullList
import net.minecraft.core.registries.BuiltInRegistries
import net.minecraft.gametest.framework.*
import net.minecraft.resources.ResourceLocation
@ -25,6 +26,8 @@ import net.minecraft.world.entity.EntityType
import net.minecraft.world.item.Item
import net.minecraft.world.item.ItemStack
import net.minecraft.world.item.context.UseOnContext
import net.minecraft.world.item.crafting.CraftingInput
import net.minecraft.world.item.crafting.RecipeType
import net.minecraft.world.level.GameType
import net.minecraft.world.level.block.Blocks
import net.minecraft.world.level.block.entity.BarrelBlockEntity
@ -340,6 +343,34 @@ fun GameTestHelper.placeItemAt(stack: ItemStack, pos: BlockPos, direction: Direc
stack.useOn(UseOnContext(player, InteractionHand.MAIN_HAND, hit))
}
/**
* Assert a recipe is not craftable.
*/
fun GameTestHelper.assertNotCraftable(vararg items: ItemStack) {
val container = NonNullList.withSize(3 * 3, ItemStack.EMPTY)
for ((i, item) in items.withIndex()) container[i] = item
val input = CraftingInput.of(3, 3, container)
val recipe = level.server.recipeManager.getRecipeFor(RecipeType.CRAFTING, input, level)
if (recipe.isPresent) fail("Expected no recipe to match $items")
}
/**
* Attempt to craft an item.
*/
fun GameTestHelper.craftItem(vararg items: ItemStack): ItemStack {
val container = NonNullList.withSize(3 * 3, ItemStack.EMPTY)
for ((i, item) in items.withIndex()) container[i] = item
val input = CraftingInput.of(3, 3, container)
val recipe = level.server.recipeManager
.getRecipeFor(RecipeType.CRAFTING, input, level)
.orElseThrow { GameTestAssertException("No recipe matches $items") }
return recipe.value.assemble(input, level.registryAccess())
}
/**
* Run a function multiple times until it succeeds.
*/

View File

@ -223,6 +223,9 @@ public interface IArguments {
/**
* Get an argument as a table.
* <p>
* The returned table may be converted into a {@link LuaTable} (using {@link ObjectLuaTable}) for easier parsing of
* table keys.
*
* @param index The argument number.
* @return The argument's value.

View File

@ -7,15 +7,18 @@ package dan200.computercraft.api.lua;
import org.jspecify.annotations.Nullable;
import java.util.Map;
import java.util.Optional;
import static dan200.computercraft.api.lua.LuaValues.*;
/**
* A view of a Lua table, which may be able to access table elements in a more optimised manner than
* {@link IArguments#getTable(int)}.
* A view of a Lua table.
* <p>
* Much like {@link IArguments}, this allows for convenient parsing of fields from a Lua table.
*
* @param <K> The type of keys in a table, will typically be a wildcard.
* @param <V> The type of values in a table, will typically be a wildcard.
* @see ObjectArguments
*/
public interface LuaTable<K, V> extends Map<K, V> {
/**
@ -29,19 +32,47 @@ public interface LuaTable<K, V> extends Map<K, V> {
return size;
}
/**
* Get an array entry as a double.
*
* @param index The index in the table, starting at 1.
* @return The entry's value.
* @throws LuaException If the value is not a number.
* @see #getFiniteDouble(int) if you require this to be finite (i.e. not infinite or NaN).
* @since 1.116
*/
default double getDouble(int index) throws LuaException {
Object value = get((double) index);
if (!(value instanceof Number number)) throw badTableItem(index, "number", getType(value));
return number.doubleValue();
}
/**
* Get a table entry as a double.
*
* @param key The name of the field in the table.
* @return The field's value.
* @throws LuaException If the value is not a number.
* @see #getFiniteDouble(String) if you require this to be finite (i.e. not infinite or NaN).
* @since 1.116
*/
default double getDouble(String key) throws LuaException {
Object value = get(key);
if (!(value instanceof Number number)) throw badField(key, "number", getType(value));
return number.doubleValue();
}
/**
* Get an array entry as an integer.
*
* @param index The index in the table, starting at 1.
* @return The table's value.
* @return The entry's value.
* @throws LuaException If the value is not an integer.
*/
default long getLong(int index) throws LuaException {
Object value = get((double) index);
if (!(value instanceof Number number)) throw badTableItem(index, "number", getType(value));
var asDouble = number.doubleValue();
if (!Double.isFinite(asDouble)) throw badTableItem(index, "number", getNumericType(asDouble));
checkFiniteIndex(index, number.doubleValue());
return number.longValue();
}
@ -49,15 +80,13 @@ public interface LuaTable<K, V> extends Map<K, V> {
* Get a table entry as an integer.
*
* @param key The name of the field in the table.
* @return The table's value.
* @return The field's value.
* @throws LuaException If the value is not an integer.
*/
default long getLong(String key) throws LuaException {
Object value = get(key);
if (!(value instanceof Number number)) throw badField(key, "number", getType(value));
var asDouble = number.doubleValue();
if (!Double.isFinite(asDouble)) throw badField(key, "number", getNumericType(asDouble));
checkFiniteField(key, number.doubleValue());
return number.longValue();
}
@ -65,7 +94,7 @@ public interface LuaTable<K, V> extends Map<K, V> {
* Get an array entry as an integer.
*
* @param index The index in the table, starting at 1.
* @return The table's value.
* @return The entry's value.
* @throws LuaException If the value is not an integer.
*/
default int getInt(int index) throws LuaException {
@ -76,13 +105,339 @@ public interface LuaTable<K, V> extends Map<K, V> {
* Get a table entry as an integer.
*
* @param key The name of the field in the table.
* @return The table's value.
* @return The field's value.
* @throws LuaException If the value is not an integer.
*/
default int getInt(String key) throws LuaException {
return (int) getLong(key);
}
/**
* Get an argument as a finite number (not infinite or NaN).
*
* @param index The index in the table, starting at 1.
* @return The entry's value.
* @throws LuaException If the value is not finite.
* @since 1.116
*/
default double getFiniteDouble(int index) throws LuaException {
return checkFiniteIndex(index, getDouble(index));
}
/**
* Get an argument as a finite number (not infinite or NaN).
*
* @param key The name of the field in the table.
* @return The field's value.
* @throws LuaException If the value is not finite.
* @since 1.116
*/
default double getFiniteDouble(String key) throws LuaException {
return checkFiniteField(key, getDouble(key));
}
/**
* Get an array entry as a boolean.
*
* @param index The index in the table, starting at 1.
* @return The entry's value.
* @throws LuaException If the value is not a boolean.
* @since 1.116
*/
default boolean getBoolean(int index) throws LuaException {
Object value = get((double) index);
if (!(value instanceof Boolean bool)) throw badTableItem(index, "boolean", getType(value));
return bool;
}
/**
* Get a table entry as a boolean.
*
* @param key The name of the field in the table.
* @return The field's value.
* @throws LuaException If the value is not a boolean.
* @since 1.116
*/
default boolean getBoolean(String key) throws LuaException {
Object value = get(key);
if (!(value instanceof Boolean bool)) throw badField(key, "boolean", getType(value));
return bool;
}
/**
* Get an array entry as a string.
*
* @param index The index in the table, starting at 1.
* @return The entry's value.
* @throws LuaException If the value is not a string.
* @since 1.116
*/
default String getString(int index) throws LuaException {
Object value = get((double) index);
if (!(value instanceof String string)) throw badTableItem(index, "string", getType(value));
return string;
}
/**
* Get a table entry as a string.
*
* @param key The name of the field in the table.
* @return The field's value.
* @throws LuaException If the value is not a string.
* @since 1.116
*/
default String getString(String key) throws LuaException {
Object value = get(key);
if (!(value instanceof String string)) throw badField(key, "string", getType(value));
return string;
}
/**
* Get an array entry as a table.
* <p>
* The returned table may be converted into a {@link LuaTable} (using {@link ObjectLuaTable}) for easier parsing of
* table keys.
*
* @param index The index in the table, starting at 1.
* @return The entry's value.
* @throws LuaException If the value is not a table.
* @since 1.116
*/
default Map<?, ?> getTable(int index) throws LuaException {
Object value = get((double) index);
if (!(value instanceof Map<?, ?> table)) throw badTableItem(index, "table", getType(value));
return table;
}
/**
* Get a table entry as a table.
* <p>
* The returned table may be converted into a {@link LuaTable} (using {@link ObjectLuaTable}) for easier parsing of
* table keys.
*
* @param key The name of the field in the table.
* @return The field's value.
* @throws LuaException If the value is not a table.
* @since 1.116
*/
default Map<?, ?> getTable(String key) throws LuaException {
Object value = get(key);
if (!(value instanceof Map<?, ?> table)) throw badField(key, "table", getType(value));
return table;
}
/**
* Get an array entry as a double.
*
* @param index The index in the table, starting at 1.
* @return The entry's value, or {@link Optional#empty()} if not present.
* @throws LuaException If the value is not a number.
* @see #getFiniteDouble(int) if you require this to be finite (i.e. not infinite or NaN).
* @since 1.116
*/
default Optional<Double> optDouble(int index) throws LuaException {
Object value = get((double) index);
if (value == null) return Optional.empty();
if (!(value instanceof Number number)) throw badTableItem(index, "number", getType(value));
return Optional.of(number.doubleValue());
}
/**
* Get a table entry as a double.
*
* @param key The name of the field in the table.
* @return The field's value, or {@link Optional#empty()} if not present.
* @throws LuaException If the value is not a number.
* @see #getFiniteDouble(String) if you require this to be finite (i.e. not infinite or NaN).
* @since 1.116
*/
default Optional<Double> optDouble(String key) throws LuaException {
Object value = get(key);
if (value == null) return Optional.empty();
if (!(value instanceof Number number)) throw badField(key, "number", getType(value));
return Optional.of(number.doubleValue());
}
/**
* Get an array entry as an integer.
*
* @param index The index in the table, starting at 1.
* @return The entry's value, or {@link Optional#empty()} if not present.
* @throws LuaException If the value is not an integer.
* @since 1.116
*/
default Optional<Long> optLong(int index) throws LuaException {
Object value = get((double) index);
if (value == null) return Optional.empty();
if (!(value instanceof Number number)) throw badTableItem(index, "number", getType(value));
checkFiniteIndex(index, number.doubleValue());
return Optional.of(number.longValue());
}
/**
* Get a table entry as an integer.
*
* @param key The name of the field in the table.
* @return The field's value, or {@link Optional#empty()} if not present.
* @throws LuaException If the value is not an integer.
* @since 1.116
*/
default Optional<Long> optLong(String key) throws LuaException {
Object value = get(key);
if (value == null) return Optional.empty();
if (!(value instanceof Number number)) throw badField(key, "number", getType(value));
checkFiniteField(key, number.doubleValue());
return Optional.of(number.longValue());
}
/**
* Get an array entry as an integer.
*
* @param index The index in the table, starting at 1.
* @return The entry's value, or {@link Optional#empty()} if not present.
* @throws LuaException If the value is not an integer.
* @since 1.116
*/
default Optional<Integer> optInt(int index) throws LuaException {
return optLong(index).map(Long::intValue);
}
/**
* Get a table entry as an integer.
*
* @param key The name of the field in the table.
* @return The field's value, or {@link Optional#empty()} if not present.
* @throws LuaException If the value is not an integer.
* @since 1.116
*/
default Optional<Integer> optInt(String key) throws LuaException {
return optLong(key).map(Long::intValue);
}
/**
* Get an argument as a finite number (not infinite or NaN).
*
* @param index The index in the table, starting at 1.
* @return The entry's value, or {@link Optional#empty()} if not present.
* @throws LuaException If the value is not finite.
* @since 1.116
*/
default Optional<Double> optFiniteDouble(int index) throws LuaException {
var value = optDouble(index);
if (value.isPresent()) checkFiniteIndex(index, value.get());
return value;
}
/**
* Get an argument as a finite number (not infinite or NaN).
*
* @param key The name of the field in the table.
* @return The field's value, or {@link Optional#empty()} if not present.
* @throws LuaException If the value is not finite.
* @since 1.116
*/
default Optional<Double> optFiniteDouble(String key) throws LuaException {
var value = optDouble(key);
if (value.isPresent()) checkFiniteField(key, value.get());
return value;
}
/**
* Get an array entry as a boolean.
*
* @param index The index in the table, starting at 1.
* @return The entry's value, or {@link Optional#empty()} if not present.
* @throws LuaException If the value is not a boolean.
* @since 1.116
*/
default Optional<Boolean> optBoolean(int index) throws LuaException {
Object value = get((double) index);
if (value == null) return Optional.empty();
if (!(value instanceof Boolean bool)) throw badTableItem(index, "boolean", getType(value));
return Optional.of(bool);
}
/**
* Get a table entry as a boolean.
*
* @param key The name of the field in the table.
* @return The field's value, or {@link Optional#empty()} if not present.
* @throws LuaException If the value is not a boolean.
* @since 1.116
*/
default Optional<Boolean> optBoolean(String key) throws LuaException {
Object value = get(key);
if (value == null) return Optional.empty();
if (!(value instanceof Boolean bool)) throw badField(key, "boolean", getType(value));
return Optional.of(bool);
}
/**
* Get an array entry as a double.
*
* @param index The index in the table, starting at 1.
* @return The entry's value, or {@link Optional#empty()} if not present.
* @throws LuaException If the value is not a string.
* @since 1.116
*/
default Optional<String> optString(int index) throws LuaException {
Object value = get((double) index);
if (value == null) return Optional.empty();
if (!(value instanceof String string)) throw badTableItem(index, "string", getType(value));
return Optional.of(string);
}
/**
* Get a table entry as a string.
*
* @param key The name of the field in the table.
* @return The field's value, or {@link Optional#empty()} if not present.
* @throws LuaException If the value is not a string.
* @since 1.116
*/
default Optional<String> optString(String key) throws LuaException {
Object value = get(key);
if (value == null) return Optional.empty();
if (!(value instanceof String string)) throw badField(key, "string", getType(value));
return Optional.of(string);
}
/**
* Get an array entry as a table.
* <p>
* The returned table may be converted into a {@link LuaTable} (using {@link ObjectLuaTable}) for easier parsing of
* table keys.
*
* @param index The index in the table, starting at 1.
* @return The entry's value, or {@link Optional#empty()} if not present.
* @throws LuaException If the value is not a table.
* @since 1.116
*/
default Optional<Map<?, ?>> optTable(int index) throws LuaException {
Object value = get((double) index);
if (value == null) return Optional.empty();
if (!(value instanceof Map<?, ?> table)) throw badTableItem(index, "table", getType(value));
return Optional.of(table);
}
/**
* Get a table entry as a table.
* <p>
* The returned table may be converted into a {@link LuaTable} (using {@link ObjectLuaTable}) for easier parsing of
* table keys.
*
* @param key The name of the field in the table.
* @return The field's value, or {@link Optional#empty()} if not present.
* @throws LuaException If the value is not a table.
* @since 1.116
*/
default Optional<Map<?, ?>> optTable(String key) throws LuaException {
Object value = get(key);
if (value == null) return Optional.empty();
if (!(value instanceof Map<?, ?> table)) throw badField(key, "table", getType(value));
return Optional.of(table);
}
@Nullable
@Override
default V put(K o, V o2) {

View File

@ -97,7 +97,7 @@ public final class LuaValues {
* @return The constructed exception, which should be thrown immediately.
*/
public static LuaException badTableItem(int index, String expected, String actual) {
return new LuaException("table item #" + index + " is not " + expected + " (got " + actual + ")");
return new LuaException("bad item #" + index + " (" + expected + " expected, got " + actual + ")");
}
/**
@ -109,7 +109,7 @@ public final class LuaValues {
* @return The constructed exception, which should be thrown immediately.
*/
public static LuaException badField(String key, String expected, String actual) {
return new LuaException("field " + key + " is not " + expected + " (got " + actual + ")");
return new LuaException("bad field '" + key + "' (" + expected + " expected, got " + actual + ")");
}
/**
@ -138,6 +138,16 @@ public final class LuaValues {
return value;
}
static double checkFiniteIndex(int index, double value) throws LuaException {
if (!Double.isFinite(value)) throw badTableItem(index, "number", getNumericType(value));
return value;
}
static double checkFiniteField(String key, double value) throws LuaException {
if (!Double.isFinite(value)) throw badField(key, "number", getNumericType(value));
return value;
}
/**
* Ensure a string is a valid enum value.
*

View File

@ -20,7 +20,6 @@ import java.util.Locale;
import java.util.Map;
import java.util.Optional;
import static dan200.computercraft.core.apis.TableHelper.*;
import static dan200.computercraft.core.util.ArgumentHelpers.assertBetween;
/**
@ -78,15 +77,14 @@ public class HTTPAPI implements ILuaAPI {
Optional<Double> timeoutArg;
if (args.get(0) instanceof Map) {
var options = args.getTable(0);
address = getStringField(options, "url");
var postString = optStringField(options, "body", null);
postBody = postString == null ? null : LuaValues.encode(postString);
headerTable = optTableField(options, "headers", Map.of());
binary = optBooleanField(options, "binary", false);
requestMethod = optStringField(options, "method", null);
redirect = optBooleanField(options, "redirect", true);
timeoutArg = optRealField(options, "timeout");
var options = new ObjectLuaTable(args.getTable(0));
address = options.getString("url");
postBody = options.optString("body").map(LuaValues::encode).orElse(null);
headerTable = options.optTable("headers").orElse(Map.of());
binary = options.optBoolean("binary").orElse(false);
requestMethod = options.optString("method").orElse(null);
redirect = options.optBoolean("redirect").orElse(true);
timeoutArg = options.optFiniteDouble("timeout");
} else {
// Get URL and post information
address = args.getString(0);
@ -151,10 +149,10 @@ public class HTTPAPI implements ILuaAPI {
Optional<Double> timeoutArg;
if (args.get(0) instanceof Map) {
var options = args.getTable(0);
address = getStringField(options, "url");
headerTable = optTableField(options, "headers", Map.of());
timeoutArg = optRealField(options, "timeout");
var options = new ObjectLuaTable(args.getTableUnsafe(0));
address = options.getString("url");
headerTable = options.optTable("headers").orElse(Map.of());
timeoutArg = options.optFiniteDouble("timeout");
} else {
address = args.getString(0);
headerTable = args.optTable(1, Map.of());

View File

@ -1,156 +0,0 @@
// SPDX-FileCopyrightText: 2018 The CC: Tweaked Developers
//
// SPDX-License-Identifier: MPL-2.0
package dan200.computercraft.core.apis;
import dan200.computercraft.api.lua.LuaException;
import dan200.computercraft.api.lua.LuaValues;
import org.jspecify.annotations.Nullable;
import java.util.Map;
import java.util.Optional;
import static dan200.computercraft.api.lua.LuaValues.getNumericType;
/**
* Various helpers for tables.
*/
public final class TableHelper {
private TableHelper() {
throw new IllegalStateException("Cannot instantiate singleton " + getClass().getName());
}
public static LuaException badKey(String key, String expected, @Nullable Object actual) {
return badKey(key, expected, LuaValues.getType(actual));
}
public static LuaException badKey(String key, String expected, String actual) {
return new LuaException("bad field '" + key + "' (" + expected + " expected, got " + actual + ")");
}
public static double getNumberField(Map<?, ?> table, String key) throws LuaException {
var value = table.get(key);
if (value instanceof Number) {
return ((Number) value).doubleValue();
} else {
throw badKey(key, "number", value);
}
}
public static int getIntField(Map<?, ?> table, String key) throws LuaException {
var value = table.get(key);
if (value instanceof Number) {
return (int) ((Number) value).longValue();
} else {
throw badKey(key, "number", value);
}
}
public static double getRealField(Map<?, ?> table, String key) throws LuaException {
return checkReal(key, getNumberField(table, key));
}
public static boolean getBooleanField(Map<?, ?> table, String key) throws LuaException {
var value = table.get(key);
if (value instanceof Boolean) {
return (Boolean) value;
} else {
throw badKey(key, "boolean", value);
}
}
public static String getStringField(Map<?, ?> table, String key) throws LuaException {
var value = table.get(key);
if (value instanceof String) {
return (String) value;
} else {
throw badKey(key, "string", value);
}
}
@SuppressWarnings("unchecked")
public static Map<Object, Object> getTableField(Map<?, ?> table, String key) throws LuaException {
var value = table.get(key);
if (value instanceof Map) {
return (Map<Object, Object>) value;
} else {
throw badKey(key, "table", value);
}
}
public static double optNumberField(Map<?, ?> table, String key, double def) throws LuaException {
var value = table.get(key);
if (value == null) {
return def;
} else if (value instanceof Number) {
return ((Number) value).doubleValue();
} else {
throw badKey(key, "number", value);
}
}
public static int optIntField(Map<?, ?> table, String key, int def) throws LuaException {
var value = table.get(key);
if (value == null) {
return def;
} else if (value instanceof Number) {
return (int) ((Number) value).longValue();
} else {
throw badKey(key, "number", value);
}
}
public static Optional<Double> optRealField(Map<?, ?> table, String key) throws LuaException {
var value = table.get(key);
if (value == null) {
return Optional.empty();
} else {
return Optional.of(getRealField(table, key));
}
}
public static double optRealField(Map<?, ?> table, String key, double def) throws LuaException {
return checkReal(key, optNumberField(table, key, def));
}
public static boolean optBooleanField(Map<?, ?> table, String key, boolean def) throws LuaException {
var value = table.get(key);
if (value == null) {
return def;
} else if (value instanceof Boolean) {
return (Boolean) value;
} else {
throw badKey(key, "boolean", value);
}
}
@Nullable
public static String optStringField(Map<?, ?> table, String key, @Nullable String def) throws LuaException {
var value = table.get(key);
if (value == null) {
return def;
} else if (value instanceof String) {
return (String) value;
} else {
throw badKey(key, "string", value);
}
}
@SuppressWarnings("unchecked")
public static Map<Object, Object> optTableField(Map<?, ?> table, String key, Map<Object, Object> def) throws LuaException {
var value = table.get(key);
if (value == null) {
return def;
} else if (value instanceof Map) {
return (Map<Object, Object>) value;
} else {
throw badKey(key, "table", value);
}
}
private static double checkReal(String key, double value) throws LuaException {
if (!Double.isFinite(value)) throw badKey(key, "number", getNumericType(value));
return value;
}
}