1
0
mirror of https://github.com/SquidDev-CC/CC-Tweaked synced 2025-08-06 22:04:39 +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" jmh = "1.37"
# Build tools # Build tools
cctJavadoc = "1.8.3" cctJavadoc = "1.8.4"
checkstyle = "10.21.2" checkstyle = "10.21.2"
errorProne-core = "2.36.0" errorProne-core = "2.36.0"
errorProne-plugin = "4.1.0" errorProne-plugin = "4.1.0"
fabric-loom = "1.9.2" fabric-loom = "1.10.3"
githubRelease = "2.5.2" githubRelease = "2.5.2"
gradleVersions = "0.50.0" gradleVersions = "0.50.0"
ideaExt = "1.1.7" ideaExt = "1.1.7"
@ -75,7 +75,7 @@ shadow = "8.3.1"
spotless = "6.23.3" spotless = "6.23.3"
taskTree = "2.1.1" taskTree = "2.1.1"
teavm = "0.11.0-SQUID.1" teavm = "0.11.0-SQUID.1"
vanillaExtract = "0.2.0" vanillaExtract = "0.2.1"
versionCatalogUpdate = "0.8.1" versionCatalogUpdate = "0.8.1"
[libraries] [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 * 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. * 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 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 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 @Override
public final void provideDetails(Map<? super String, Object> data, DataComponentHolder holder) { 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.item.component.DyedItemColor;
import net.minecraft.world.level.ItemLike; import net.minecraft.world.level.ItemLike;
import org.jspecify.annotations.Nullable; import org.jspecify.annotations.Nullable;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.File; import java.io.File;
import java.io.IOException; import java.io.IOException;
@ -69,6 +71,8 @@ import java.util.function.Supplier;
* @see ModRegistry The common registry for actual game objects. * @see ModRegistry The common registry for actual game objects.
*/ */
public final class ClientRegistry { public final class ClientRegistry {
private static final Logger LOG = LoggerFactory.getLogger(ClientRegistry.class);
private ClientRegistry() { private ClientRegistry() {
} }
@ -192,7 +196,18 @@ public final class ClientRegistry {
} }
public static void registerShaders(ResourceProvider resources, BiConsumer<ShaderInstance, Consumer<ShaderInstance>> load) throws IOException { 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( 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.client.render.text.FixedWidthFontRenderer;
import dan200.computercraft.core.terminal.Terminal; import dan200.computercraft.core.terminal.Terminal;
import dan200.computercraft.core.util.Colour; import dan200.computercraft.core.util.Colour;
import dan200.computercraft.shared.ModRegistry;
import dan200.computercraft.shared.lectern.CustomLecternBlockEntity; import dan200.computercraft.shared.lectern.CustomLecternBlockEntity;
import dan200.computercraft.shared.media.items.PrintoutData; import dan200.computercraft.shared.media.items.PrintoutData;
import dan200.computercraft.shared.media.items.PrintoutItem; import dan200.computercraft.shared.media.items.PrintoutItem;
@ -59,9 +60,9 @@ public class CustomLecternRenderer implements BlockEntityRenderer<CustomLecternB
poseStack.translate(0, -0.125f, 0); poseStack.translate(0, -0.125f, 0);
var item = lectern.getItem(); var item = lectern.getItem();
if (item.getItem() instanceof PrintoutItem printout) { if (item.getItem() instanceof PrintoutItem) {
var vertexConsumer = LecternPrintoutModel.MATERIAL.buffer(buffer, RenderType::entitySolid); 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); printoutModel.renderBook(poseStack, vertexConsumer, packedLight, packedOverlay);
} else { } else {
printoutModel.renderPages(poseStack, vertexConsumer, packedLight, packedOverlay, PrintoutData.getOrEmpty(item).pages()); 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 com.mojang.math.Axis;
import dan200.computercraft.shared.ModRegistry; import dan200.computercraft.shared.ModRegistry;
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.renderer.MultiBufferSource; import net.minecraft.client.renderer.MultiBufferSource;
import net.minecraft.world.entity.EntityType; import net.minecraft.world.entity.EntityType;
import net.minecraft.world.entity.decoration.ItemFrame; 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 pageData = stack.getOrDefault(ModRegistry.DataComponents.PRINTOUT.get(), PrintoutData.EMPTY);
var pages = pageData.pages(); 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 width = LINE_LENGTH * FONT_WIDTH + X_TEXT_MARGIN * 2;
double height = LINES_PER_PAGE * FONT_HEIGHT + Y_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.client.renderer.ShaderInstance;
import net.minecraft.resources.ResourceLocation; import net.minecraft.resources.ResourceLocation;
import net.minecraft.server.packs.resources.ResourceProvider; import net.minecraft.server.packs.resources.ResourceProvider;
import org.apache.commons.io.function.IOSupplier;
import org.jspecify.annotations.Nullable; import org.jspecify.annotations.Nullable;
import java.io.IOException; import java.io.IOException;
import java.util.Objects; import java.util.Objects;
import java.util.function.BiConsumer;
import java.util.function.Consumer; import java.util.function.Consumer;
/** /**
@ -68,9 +68,12 @@ public class RenderTypes {
return Objects.requireNonNull(GameRenderer.getRendertypeTextShader(), "Text shader has not been registered"); return Objects.requireNonNull(GameRenderer.getRendertypeTextShader(), "Text shader has not been registered");
} }
public static void registerShaders(ResourceProvider resources, BiConsumer<ShaderInstance, Consumer<ShaderInstance>> load) throws IOException { public interface ShaderLoader {
load.accept( void tryLoad(String name, IOSupplier<ShaderInstance> create, Consumer<@Nullable ShaderInstance> accept) throws IOException;
new MonitorTextureBufferShader( }
public static void registerShaders(ResourceProvider resources, ShaderLoader load) throws IOException {
load.tryLoad("monitor shader", () -> new MonitorTextureBufferShader(
resources, resources,
ComputerCraftAPI.MOD_ID + "/monitor_tbo", ComputerCraftAPI.MOD_ID + "/monitor_tbo",
MONITOR_TBO.format() MONITOR_TBO.format()

View File

@ -284,11 +284,11 @@ public final class ModRegistry {
} }
public static final RegistryEntry<PrintoutItem> PRINTED_PAGE = REGISTRY.register("printed_page", 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", 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", 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> SPEAKER = ofBlock(Blocks.SPEAKER, BlockItem::new);
public static final RegistryEntry<BlockItem> DISK_DRIVE = ofBlock(Blocks.DISK_DRIVE, 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; import java.util.List;
public class PrintoutItem extends Item { public class PrintoutItem extends Item {
public enum Type { public PrintoutItem(Properties settings) {
PAGE,
PAGES,
BOOK
}
private final Type type;
public PrintoutItem(Properties settings, Type type) {
super(settings); super(settings);
this.type = type;
} }
@Override @Override
@ -49,8 +40,4 @@ public class PrintoutItem extends Item {
} }
return new InteractionResultHolder<>(InteractionResult.sidedSuccess(world.isClientSide), stack); 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) { static boolean isPaper(ItemStack stack) {
var item = stack.getItem(); return stack.is(Items.PAPER) || stack.is(ModRegistry.Items.PRINTED_PAGE.get());
return item == Items.PAPER || item == ModRegistry.Items.PRINTED_PAGE.get();
} }
private boolean canInputPage() { private boolean canInputPage() {

View File

@ -13,7 +13,9 @@ import dan200.computercraft.core.metrics.Metrics;
import dan200.computercraft.core.metrics.MetricsObserver; import dan200.computercraft.core.metrics.MetricsObserver;
import dan200.computercraft.shared.peripheral.generic.methods.AbstractInventoryMethods; import dan200.computercraft.shared.peripheral.generic.methods.AbstractInventoryMethods;
import dan200.computercraft.shared.turtle.core.*; import dan200.computercraft.shared.turtle.core.*;
import org.jspecify.annotations.Nullable;
import java.util.Map;
import java.util.Optional; import java.util.Optional;
/** /**
@ -659,6 +661,7 @@ public class TurtleAPI implements ILuaAPI {
* @cc.treturn [2] string The reason equipping this item failed. * @cc.treturn [2] string The reason equipping this item failed.
* @cc.since 1.6 * @cc.since 1.6
* @see #equipRight() * @see #equipRight()
* @see #getEquippedLeft()
*/ */
@LuaFunction @LuaFunction
public final MethodResult equipLeft() { public final MethodResult equipLeft() {
@ -678,12 +681,45 @@ public class TurtleAPI implements ILuaAPI {
* @cc.treturn [2] string The reason equipping this item failed. * @cc.treturn [2] string The reason equipping this item failed.
* @cc.since 1.6 * @cc.since 1.6
* @see #equipLeft() * @see #equipLeft()
* @see #getEquippedRight()
*/ */
@LuaFunction @LuaFunction
public final MethodResult equipRight() { public final MethodResult equipRight() {
return trackCommand(new TurtleEquipCommand(TurtleSide.RIGHT)); 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. * Get information about the block in front of the turtle.
* *

View File

@ -1,7 +1,7 @@
{ {
"parent": "computercraft:block/turtle_base", "parent": "computercraft:block/turtle_base",
"textures": { "textures": {
"texture": "computercraft:block/turtle_colour", "particle": "computercraft:block/turtle_colour_body_front",
"body_back": "computercraft:block/turtle_colour_body_back", "body_back": "computercraft:block/turtle_colour_body_back",
"body_backpack": "computercraft:block/turtle_colour_body_backpack", "body_backpack": "computercraft:block/turtle_colour_body_backpack",
"body_bottom": "computercraft:block/turtle_colour_body_bottom", "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()); 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) { public static Matcher<ItemStack> isStack(ItemStack stack) {
return new ItemStackMatcher(stack); return new ItemStackMatcher(stack);
} }

View File

@ -5,9 +5,20 @@
package dan200.computercraft.gametest package dan200.computercraft.gametest
import dan200.computercraft.gametest.api.* 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.GameTestGenerator
import net.minecraft.gametest.framework.GameTestHelper import net.minecraft.gametest.framework.GameTestHelper
import net.minecraft.gametest.framework.TestFunction 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 { class Printout_Test {
/** /**
@ -56,4 +67,54 @@ class Printout_Test {
thenExecute { helper.level.dayTime = Times.NOON } 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 com.mojang.authlib.GameProfile
import dan200.computercraft.gametest.api.Structures import dan200.computercraft.gametest.api.Structures
import dan200.computercraft.gametest.api.craftItem
import dan200.computercraft.gametest.api.sequence import dan200.computercraft.gametest.api.sequence
import dan200.computercraft.shared.ModRegistry import dan200.computercraft.shared.ModRegistry
import net.minecraft.core.NonNullList
import net.minecraft.core.component.DataComponentPatch 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.GameTestAssertException
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.item.component.ResolvableProfile 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 org.junit.jupiter.api.Assertions.assertEquals
import java.util.* import java.util.*
@ -31,16 +28,10 @@ class Recipe_Test {
@GameTest(template = Structures.DEFAULT) @GameTest(template = Structures.DEFAULT)
fun Craft_result_has_nbt(context: GameTestHelper) = context.sequence { fun Craft_result_has_nbt(context: GameTestHelper) = context.sequence {
thenExecute { thenExecute {
val items = NonNullList.withSize(3 * 3, ItemStack.EMPTY) val result = context.craftItem(
items[0] = ItemStack(Items.SKELETON_SKULL) ItemStack(Items.SKELETON_SKULL),
items[1] = ItemStack(ModRegistry.Items.COMPUTER_ADVANCED.get()) 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 profile = GameProfile(UUID.fromString("f3c8d69b-0776-4512-8434-d1b2165909eb"), "dan200") 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.ComputerCraftAPI
import dan200.computercraft.api.ComputerCraftTags 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.detail.VanillaDetailRegistries
import dan200.computercraft.api.lua.ObjectArguments import dan200.computercraft.api.lua.ObjectArguments
import dan200.computercraft.api.turtle.ITurtleUpgrade 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.GameTestHelperAccessor
import dan200.computercraft.mixin.gametest.GameTestInfoAccessor import dan200.computercraft.mixin.gametest.GameTestInfoAccessor
import dan200.computercraft.shared.ModRegistry 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.CableBlock
import dan200.computercraft.shared.peripheral.modem.wired.CableModemVariant import dan200.computercraft.shared.peripheral.modem.wired.CableModemVariant
import dan200.computercraft.shared.peripheral.monitor.MonitorBlock import dan200.computercraft.shared.peripheral.monitor.MonitorBlock
@ -453,16 +453,16 @@ class Turtle_Test {
thenExecute { thenExecute {
VanillaDetailRegistries.ITEM_STACK.addProvider( VanillaDetailRegistries.ITEM_STACK.addProvider(
object : object :
BasicItemDetailProvider<PrintoutItem>("printout", PrintoutItem::class.java) { ComponentDetailProvider<PrintoutData>("printout", ModRegistry.DataComponents.PRINTOUT.get()) {
override fun provideDetails(data: MutableMap<in String, Any>, stack: ItemStack, item: PrintoutItem) { override fun provideComponentDetails(data: MutableMap<in String, Any>, component: PrintoutData) {
data["type"] = item.type.toString().lowercase() data["pages"] = component.pages()
} }
}, },
) )
} }
thenOnComputer { thenOnComputer {
val details = getTurtleItemDetail(detailed = true) 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)" "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.commands.arguments.blocks.BlockInput
import net.minecraft.core.BlockPos import net.minecraft.core.BlockPos
import net.minecraft.core.Direction import net.minecraft.core.Direction
import net.minecraft.core.NonNullList
import net.minecraft.core.registries.BuiltInRegistries import net.minecraft.core.registries.BuiltInRegistries
import net.minecraft.gametest.framework.* import net.minecraft.gametest.framework.*
import net.minecraft.resources.ResourceLocation 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.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.item.crafting.CraftingInput
import net.minecraft.world.item.crafting.RecipeType
import net.minecraft.world.level.GameType import net.minecraft.world.level.GameType
import net.minecraft.world.level.block.Blocks import net.minecraft.world.level.block.Blocks
import net.minecraft.world.level.block.entity.BarrelBlockEntity 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)) 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. * Run a function multiple times until it succeeds.
*/ */

View File

@ -223,6 +223,9 @@ public interface IArguments {
/** /**
* Get an argument as a table. * 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. * @param index The argument number.
* @return The argument's value. * @return The argument's value.

View File

@ -7,15 +7,18 @@ package dan200.computercraft.api.lua;
import org.jspecify.annotations.Nullable; import org.jspecify.annotations.Nullable;
import java.util.Map; import java.util.Map;
import java.util.Optional;
import static dan200.computercraft.api.lua.LuaValues.*; 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 * A view of a Lua table.
* {@link IArguments#getTable(int)}. * <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 <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. * @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> { public interface LuaTable<K, V> extends Map<K, V> {
/** /**
@ -29,19 +32,47 @@ public interface LuaTable<K, V> extends Map<K, V> {
return size; 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. * Get an array entry as an integer.
* *
* @param index The index in the table, starting at 1. * @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. * @throws LuaException If the value is not an integer.
*/ */
default long getLong(int index) throws LuaException { default long getLong(int index) throws LuaException {
Object value = get((double) index); Object value = get((double) index);
if (!(value instanceof Number number)) throw badTableItem(index, "number", getType(value)); if (!(value instanceof Number number)) throw badTableItem(index, "number", getType(value));
checkFiniteIndex(index, number.doubleValue());
var asDouble = number.doubleValue();
if (!Double.isFinite(asDouble)) throw badTableItem(index, "number", getNumericType(asDouble));
return number.longValue(); return number.longValue();
} }
@ -49,15 +80,13 @@ public interface LuaTable<K, V> extends Map<K, V> {
* Get a table entry as an integer. * Get a table entry as an integer.
* *
* @param key The name of the field in the table. * @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. * @throws LuaException If the value is not an integer.
*/ */
default long getLong(String key) throws LuaException { default long getLong(String key) throws LuaException {
Object value = get(key); Object value = get(key);
if (!(value instanceof Number number)) throw badField(key, "number", getType(value)); if (!(value instanceof Number number)) throw badField(key, "number", getType(value));
checkFiniteField(key, number.doubleValue());
var asDouble = number.doubleValue();
if (!Double.isFinite(asDouble)) throw badField(key, "number", getNumericType(asDouble));
return number.longValue(); return number.longValue();
} }
@ -65,7 +94,7 @@ public interface LuaTable<K, V> extends Map<K, V> {
* Get an array entry as an integer. * Get an array entry as an integer.
* *
* @param index The index in the table, starting at 1. * @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. * @throws LuaException If the value is not an integer.
*/ */
default int getInt(int index) throws LuaException { 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. * Get a table entry as an integer.
* *
* @param key The name of the field in the table. * @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. * @throws LuaException If the value is not an integer.
*/ */
default int getInt(String key) throws LuaException { default int getInt(String key) throws LuaException {
return (int) getLong(key); 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 @Nullable
@Override @Override
default V put(K o, V o2) { 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. * @return The constructed exception, which should be thrown immediately.
*/ */
public static LuaException badTableItem(int index, String expected, String actual) { 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. * @return The constructed exception, which should be thrown immediately.
*/ */
public static LuaException badField(String key, String expected, String actual) { 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; 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. * 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.Map;
import java.util.Optional; import java.util.Optional;
import static dan200.computercraft.core.apis.TableHelper.*;
import static dan200.computercraft.core.util.ArgumentHelpers.assertBetween; import static dan200.computercraft.core.util.ArgumentHelpers.assertBetween;
/** /**
@ -78,15 +77,14 @@ public class HTTPAPI implements ILuaAPI {
Optional<Double> timeoutArg; Optional<Double> timeoutArg;
if (args.get(0) instanceof Map) { if (args.get(0) instanceof Map) {
var options = args.getTable(0); var options = new ObjectLuaTable(args.getTable(0));
address = getStringField(options, "url"); address = options.getString("url");
var postString = optStringField(options, "body", null); postBody = options.optString("body").map(LuaValues::encode).orElse(null);
postBody = postString == null ? null : LuaValues.encode(postString); headerTable = options.optTable("headers").orElse(Map.of());
headerTable = optTableField(options, "headers", Map.of()); binary = options.optBoolean("binary").orElse(false);
binary = optBooleanField(options, "binary", false); requestMethod = options.optString("method").orElse(null);
requestMethod = optStringField(options, "method", null); redirect = options.optBoolean("redirect").orElse(true);
redirect = optBooleanField(options, "redirect", true); timeoutArg = options.optFiniteDouble("timeout");
timeoutArg = optRealField(options, "timeout");
} else { } else {
// Get URL and post information // Get URL and post information
address = args.getString(0); address = args.getString(0);
@ -151,10 +149,10 @@ public class HTTPAPI implements ILuaAPI {
Optional<Double> timeoutArg; Optional<Double> timeoutArg;
if (args.get(0) instanceof Map) { if (args.get(0) instanceof Map) {
var options = args.getTable(0); var options = new ObjectLuaTable(args.getTableUnsafe(0));
address = getStringField(options, "url"); address = options.getString("url");
headerTable = optTableField(options, "headers", Map.of()); headerTable = options.optTable("headers").orElse(Map.of());
timeoutArg = optRealField(options, "timeout"); timeoutArg = options.optFiniteDouble("timeout");
} else { } else {
address = args.getString(0); address = args.getString(0);
headerTable = args.optTable(1, Map.of()); 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;
}
}