mirror of
				https://github.com/SquidDev-CC/CC-Tweaked
				synced 2025-10-31 13:42:59 +00:00 
			
		
		
		
	Merge branch 'mc-1.20.x' into mc-1.21.x
This commit is contained in:
		| @@ -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 |  | ||||||
|     } |  | ||||||
| } |  | ||||||
| @@ -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] | ||||||
|   | |||||||
| @@ -52,9 +52,9 @@ public abstract class ComponentDetailProvider<T> implements DetailProvider<DataC | |||||||
|      * 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) { | ||||||
|   | |||||||
| @@ -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( | ||||||
|   | |||||||
| @@ -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()); | ||||||
|   | |||||||
| @@ -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; | ||||||
|   | |||||||
| @@ -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() | ||||||
|   | |||||||
| @@ -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); | ||||||
|   | |||||||
| @@ -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; |  | ||||||
|     } |  | ||||||
| } | } | ||||||
|   | |||||||
| @@ -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() { | ||||||
|   | |||||||
| @@ -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. | ||||||
|      * |      * | ||||||
|   | |||||||
| @@ -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", | ||||||
|   | |||||||
| @@ -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); | ||||||
|     } |     } | ||||||
|   | |||||||
| @@ -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)) | ||||||
|  |     } | ||||||
| } | } | ||||||
|   | |||||||
| @@ -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") | ||||||
| 
 | 
 | ||||||
|   | |||||||
| @@ -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)" | ||||||
|             } |             } | ||||||
|         } |         } | ||||||
|   | |||||||
| @@ -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. | ||||||
|  */ |  */ | ||||||
|   | |||||||
| @@ -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. | ||||||
|   | |||||||
| @@ -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) { | ||||||
|   | |||||||
| @@ -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. | ||||||
|      * |      * | ||||||
|   | |||||||
| @@ -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()); | ||||||
|   | |||||||
| @@ -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; |  | ||||||
|     } |  | ||||||
| } |  | ||||||
		Reference in New Issue
	
	Block a user
	 Jonathan Coates
					Jonathan Coates