1
0
mirror of https://github.com/SquidDev-CC/CC-Tweaked synced 2025-01-11 18:00:29 +00:00

Add abstract classes for our generic peripherals

This commit adds abstract classes to describe the interface for our
mod-loader-specific generic peripherals (inventories, fluid storage,
item storage).

This offers several advantages:
 - Javadoc to illuaminate conversion no longer needs the Forge project
   (just core and common).

 - Ensures we have a consistent interface between Forge and Fabric.

Note, this does /not/ implement fluid or energy storage for Fabric. We
probably could do fluid without issue, but not something worth doing
right now.
This commit is contained in:
Jonathan Coates 2023-11-22 18:20:15 +00:00
parent fe826f5c9c
commit 84a799d27a
No known key found for this signature in database
GPG Key ID: B9E431FF07C98D06
15 changed files with 469 additions and 372 deletions

View File

@ -51,7 +51,7 @@ jqwik = "1.7.4"
junit = "5.10.0"
# Build tools
cctJavadoc = "1.8.1"
cctJavadoc = "1.8.2"
checkstyle = "10.12.3"
curseForgeGradle = "1.0.14"
errorProne-core = "2.21.1"

View File

@ -6,7 +6,7 @@
(sources
/doc/
/projects/forge/build/docs/luaJavadoc/
/projects/common/build/docs/luaJavadoc/
/projects/core/src/main/resources/data/computercraft/lua/bios.lua
/projects/core/src/main/resources/data/computercraft/lua/rom/
/projects/core/src/test/resources/test-rom
@ -36,7 +36,7 @@
(library-path
/doc/stub/
/projects/forge/build/docs/luaJavadoc/
/projects/common/build/docs/luaJavadoc/
/projects/core/src/main/resources/data/computercraft/lua/rom/apis/
/projects/core/src/main/resources/data/computercraft/lua/rom/apis/command/
@ -88,7 +88,7 @@
(/doc/stub/
/projects/core/src/main/resources/data/computercraft/lua/bios.lua
/projects/core/src/main/resources/data/computercraft/lua/rom/apis/
/projects/forge/build/docs/luaJavadoc/)
/projects/common/build/docs/luaJavadoc/)
(linters -var:unused-global)
(lint (allow-toplevel-global true)))

View File

@ -2,13 +2,12 @@
//
// SPDX-License-Identifier: MPL-2.0
import cc.tweaked.gradle.annotationProcessorEverywhere
import cc.tweaked.gradle.clientClasses
import cc.tweaked.gradle.commonClasses
import cc.tweaked.gradle.*
plugins {
id("cc-tweaked.vanilla")
id("cc-tweaked.gametest")
id("cc-tweaked.illuaminate")
id("cc-tweaked.publishing")
}
@ -19,6 +18,10 @@ minecraft {
)
}
configurations {
register("cctJavadoc")
}
dependencies {
// Pull in our other projects. See comments in MinecraftConfigurations on this nastiness.
implementation(project(":core"))
@ -41,4 +44,53 @@ dependencies {
testModImplementation(libs.bundles.kotlin)
testFixturesImplementation(testFixtures(project(":core")))
"cctJavadoc"(libs.cctJavadoc)
}
illuaminate {
version.set(libs.versions.illuaminate)
}
val luaJavadoc by tasks.registering(Javadoc::class) {
description = "Generates documentation for Java-side Lua functions."
group = JavaBasePlugin.DOCUMENTATION_GROUP
val sourceSets = listOf(sourceSets.main.get(), project(":core").sourceSets.main.get())
for (sourceSet in sourceSets) {
source(sourceSet.java)
classpath += sourceSet.compileClasspath
}
destinationDir = layout.buildDirectory.dir("docs/luaJavadoc").get().asFile
val options = options as StandardJavadocDocletOptions
options.docletpath = configurations["cctJavadoc"].files.toList()
options.doclet = "cc.tweaked.javadoc.LuaDoclet"
options.addStringOption("project-root", rootProject.file(".").absolutePath)
options.noTimestamp(false)
javadocTool.set(
javaToolchains.javadocToolFor {
languageVersion.set(cc.tweaked.gradle.CCTweakedPlugin.JAVA_VERSION)
},
)
}
val lintLua by tasks.registering(IlluaminateExec::class) {
group = JavaBasePlugin.VERIFICATION_GROUP
description = "Lint Lua (and Lua docs) with illuaminate"
// Config files
inputs.file(rootProject.file("illuaminate.sexp")).withPropertyName("illuaminate.sexp")
// Sources
inputs.files(rootProject.fileTree("doc")).withPropertyName("docs")
inputs.files(project(":core").fileTree("src/main/resources/data/computercraft/lua")).withPropertyName("lua rom")
inputs.files(luaJavadoc)
args = listOf("lint")
workingDir = rootProject.projectDir
doFirst { if (System.getenv("GITHUB_ACTIONS") != null) println("::add-matcher::.github/matchers/illuaminate.json") }
doLast { if (System.getenv("GITHUB_ACTIONS") != null) println("::remove-matcher owner=illuaminate::") }
}

View File

@ -0,0 +1,53 @@
// SPDX-FileCopyrightText: 2020 The CC: Tweaked Developers
//
// SPDX-License-Identifier: MPL-2.0
package dan200.computercraft.shared.peripheral.generic.methods;
import dan200.computercraft.api.ComputerCraftAPI;
import dan200.computercraft.api.lua.LuaFunction;
import dan200.computercraft.api.peripheral.GenericPeripheral;
import dan200.computercraft.api.peripheral.PeripheralType;
/**
* Methods for interacting with blocks which store energy.
* <p>
* This works with energy storage blocks, as well as generators and machines which consume energy.
* <p>
* > [!NOTE]
* > Due to limitations with Forge's energy API, it is not possible to measure throughput (i.e. FE used/generated per
* > tick).
*
* @param <T> The type for energy storage.
* @cc.module energy_storage
* @cc.since 1.94.0
*/
public abstract class AbstractEnergyMethods<T> implements GenericPeripheral {
@Override
public final PeripheralType getType() {
return PeripheralType.ofAdditional("energy_storage");
}
@Override
public final String id() {
return ComputerCraftAPI.MOD_ID + ":energy";
}
/**
* Get the energy of this block.
*
* @param energy The current energy storage.
* @return The energy stored in this block, in FE.
*/
@LuaFunction(mainThread = true)
public abstract int getEnergy(T energy);
/**
* Get the maximum amount of energy this block can store.
*
* @param energy The current energy storage.
* @return The energy capacity of this block.
*/
@LuaFunction(mainThread = true)
public abstract int getEnergyCapacity(T energy);
}

View File

@ -0,0 +1,92 @@
// SPDX-FileCopyrightText: 2020 The CC: Tweaked Developers
//
// SPDX-License-Identifier: MPL-2.0
package dan200.computercraft.shared.peripheral.generic.methods;
import dan200.computercraft.api.ComputerCraftAPI;
import dan200.computercraft.api.lua.LuaException;
import dan200.computercraft.api.lua.LuaFunction;
import dan200.computercraft.api.peripheral.GenericPeripheral;
import dan200.computercraft.api.peripheral.IComputerAccess;
import dan200.computercraft.api.peripheral.PeripheralType;
import java.util.Map;
import java.util.Optional;
/**
* Methods for interacting with tanks and other fluid storage blocks.
*
* @param <T> The type for fluid inventories.
* @cc.module fluid_storage
* @cc.since 1.94.0
*/
public abstract class AbstractFluidMethods<T> implements GenericPeripheral {
@Override
public final PeripheralType getType() {
return PeripheralType.ofAdditional("fluid_storage");
}
@Override
public final String id() {
return ComputerCraftAPI.MOD_ID + ":fluid";
}
/**
* Get all "tanks" in this fluid storage.
* <p>
* Each tank either contains some amount of fluid or is empty. Tanks with fluids inside will return some basic
* information about the fluid, including its name and amount.
* <p>
* The returned table is sparse, and so empty tanks will be `nil` - it is recommended to loop over using [`pairs`]
* rather than [`ipairs`].
*
* @param fluids The current fluid handler.
* @return All tanks.
* @cc.treturn { (table|nil)... } All tanks in this fluid storage.
*/
@LuaFunction(mainThread = true)
public abstract Map<Integer, Map<String, ?>> tanks(T fluids);
/**
* Move a fluid from one fluid container to another connected one.
* <p>
* This allows you to pull fluid in the current fluid container to another container <em>on the same wired
* network</em>. Both containers must attached to wired modems which are connected via a cable.
*
* @param from Container to move fluid from.
* @param computer The current computer.
* @param toName The name of the peripheral/container to push to. This is the string given to [`peripheral.wrap`],
* and displayed by the wired modem.
* @param limit The maximum amount of fluid to move.
* @param fluidName The fluid to move. If not given, an arbitrary fluid will be chosen.
* @return The amount of moved fluid.
* @throws LuaException If the peripheral to transfer to doesn't exist or isn't an fluid container.
* @cc.see peripheral.getName Allows you to get the name of a [wrapped][`peripheral.wrap`] peripheral.
*/
@LuaFunction(mainThread = true)
public abstract int pushFluid(
T from, IComputerAccess computer, String toName, Optional<Integer> limit, Optional<String> fluidName
) throws LuaException;
/**
* Move a fluid from a connected fluid container into this oneone.
* <p>
* This allows you to pull fluid in the current fluid container from another container <em>on the same wired
* network</em>. Both containers must attached to wired modems which are connected via a cable.
*
* @param to Container to move fluid to.
* @param computer The current computer.
* @param fromName The name of the peripheral/container to push to. This is the string given to [`peripheral.wrap`],
* and displayed by the wired modem.
* @param limit The maximum amount of fluid to move.
* @param fluidName The fluid to move. If not given, an arbitrary fluid will be chosen.
* @return The amount of moved fluid.
* @throws LuaException If the peripheral to transfer to doesn't exist or isn't an fluid container.
* @cc.see peripheral.getName Allows you to get the name of a [wrapped][`peripheral.wrap`] peripheral.
*/
@LuaFunction(mainThread = true)
public abstract int pullFluid(
T to, IComputerAccess computer, String fromName, Optional<Integer> limit, Optional<String> fluidName
) throws LuaException;
}

View File

@ -0,0 +1,197 @@
// SPDX-FileCopyrightText: 2020 The CC: Tweaked Developers
//
// SPDX-License-Identifier: LicenseRef-CCPL
package dan200.computercraft.shared.peripheral.generic.methods;
import dan200.computercraft.api.ComputerCraftAPI;
import dan200.computercraft.api.lua.ILuaContext;
import dan200.computercraft.api.lua.LuaException;
import dan200.computercraft.api.lua.LuaFunction;
import dan200.computercraft.api.peripheral.GenericPeripheral;
import dan200.computercraft.api.peripheral.IComputerAccess;
import dan200.computercraft.api.peripheral.PeripheralType;
import javax.annotation.Nullable;
import java.util.Map;
import java.util.Optional;
/**
* Methods for interacting with inventories.
*
* @param <T> The type for inventories.
* @cc.module inventory
* @cc.since 1.94.0
*/
public abstract class AbstractInventoryMethods<T> implements GenericPeripheral {
@Override
public final PeripheralType getType() {
return PeripheralType.ofAdditional("inventory");
}
@Override
public final String id() {
return ComputerCraftAPI.MOD_ID + ":inventory";
}
/**
* Get the size of this inventory.
*
* @param inventory The current inventory.
* @return The number of slots in this inventory.
*/
@LuaFunction(mainThread = true)
public abstract int size(T inventory);
/**
* List all items in this inventory. This returns a table, with an entry for each slot.
* <p>
* Each item in the inventory is represented by a table containing some basic information, much like
* {@link dan200.computercraft.shared.turtle.apis.TurtleAPI#getItemDetail(ILuaContext, Optional, Optional)}
* includes. More information can be fetched with {@link #getItemDetail}. The table contains the item `name`, the
* `count` and an a (potentially nil) hash of the item's `nbt.` This NBT data doesn't contain anything useful, but
* allows you to distinguish identical items.
* <p>
* The returned table is sparse, and so empty slots will be `nil` - it is recommended to loop over using [`pairs`]
* rather than [`ipairs`].
*
* @param inventory The current inventory.
* @return All items in this inventory.
* @cc.treturn { (table|nil)... } All items in this inventory.
* @cc.usage Find an adjacent chest and print all items in it.
*
* <pre>{@code
* local chest = peripheral.find("minecraft:chest")
* for slot, item in pairs(chest.list()) do
* print(("%d x %s in slot %d"):format(item.count, item.name, slot))
* end
* }</pre>
*/
@LuaFunction(mainThread = true)
public abstract Map<Integer, Map<String, ?>> list(T inventory);
/**
* Get detailed information about an item.
* <p>
* The returned information contains the same information as each item in
* {@link #list}, as well as additional details like the display name
* (`displayName`), and item and item durability (`damage`, `maxDamage`, `durability`).
* <p>
* Some items include more information (such as enchantments) - it is
* recommended to print it out using [`textutils.serialize`] or in the Lua
* REPL, to explore what is available.
* <p>
* > [Deprecated fields][!INFO]
* > Older versions of CC: Tweaked exposed an {@code itemGroups} field, listing the
* > creative tabs an item was available under. This information is no longer available on
* > more recent versions of the game, and so this field will always be empty. Do not use this
* > field in new code!
*
* @param inventory The current inventory.
* @param slot The slot to get information about.
* @return Information about the item in this slot, or {@code nil} if not present.
* @throws LuaException If the slot is out of range.
* @cc.treturn table Information about the item in this slot, or {@code nil} if not present.
* @cc.usage Print some information about the first in a chest.
*
* <pre>{@code
* local chest = peripheral.find("minecraft:chest")
* local item = chest.getItemDetail(1)
* if not item then print("No item") return end
*
* print(("%s (%s)"):format(item.displayName, item.name))
* print(("Count: %d/%d"):format(item.count, item.maxCount))
*
* if item.damage then
* print(("Damage: %d/%d"):format(item.damage, item.maxDamage))
* end
* }</pre>
*/
@Nullable
@LuaFunction(mainThread = true)
public abstract Map<String, ?> getItemDetail(T inventory, int slot) throws LuaException;
/**
* Get the maximum number of items which can be stored in this slot.
* <p>
* Typically this will be limited to 64 items. However, some inventories (such as barrels or caches) can store
* hundreds or thousands of items in one slot.
*
* @param inventory Inventory to probe.
* @param slot The slot
* @return The maximum number of items in this slot.
* @throws LuaException If the slot is out of range.
* @cc.usage Count the maximum number of items an adjacent chest can hold.
* <pre>{@code
* local chest = peripheral.find("minecraft:chest")
* local total = 0
* for i = 1, chest.size() do
* total = total + chest.getItemLimit(i)
* end
* print(total)
* }</pre>
* @cc.since 1.96.0
*/
@LuaFunction(mainThread = true)
public abstract long getItemLimit(T inventory, int slot) throws LuaException;
/**
* Push items from one inventory to another connected one.
* <p>
* This allows you to push an item in an inventory to another inventory <em>on the same wired network</em>. Both
* inventories must attached to wired modems which are connected via a cable.
*
* @param from Inventory to move items from.
* @param computer The current computer.
* @param toName The name of the peripheral/inventory to push to. This is the string given to [`peripheral.wrap`],
* and displayed by the wired modem.
* @param fromSlot The slot in the current inventory to move items to.
* @param limit The maximum number of items to move. Defaults to the current stack limit.
* @param toSlot The slot in the target inventory to move to. If not given, the item will be inserted into any slot.
* @return The number of transferred items.
* @throws LuaException If the peripheral to transfer to doesn't exist or isn't an inventory.
* @throws LuaException If either source or destination slot is out of range.
* @cc.see peripheral.getName Allows you to get the name of a [wrapped][`peripheral.wrap`] peripheral.
* @cc.usage Wrap two chests, and push an item from one to another.
* <pre>{@code
* local chest_a = peripheral.wrap("minecraft:chest_0")
* local chest_b = peripheral.wrap("minecraft:chest_1")
*
* chest_a.pushItems(peripheral.getName(chest_b), 1)
* }</pre>
*/
@LuaFunction(mainThread = true)
public abstract int pushItems(
T from, IComputerAccess computer, String toName, int fromSlot, Optional<Integer> limit, Optional<Integer> toSlot
) throws LuaException;
/**
* Pull items from a connected inventory into this one.
* <p>
* This allows you to transfer items between inventories <em>on the same wired network</em>. Both this and the source
* inventory must attached to wired modems which are connected via a cable.
*
* @param to Inventory to move items to.
* @param computer The current computer.
* @param fromName The name of the peripheral/inventory to pull from. This is the string given to [`peripheral.wrap`],
* and displayed by the wired modem.
* @param fromSlot The slot in the source inventory to move items from.
* @param limit The maximum number of items to move. Defaults to the current stack limit.
* @param toSlot The slot in current inventory to move to. If not given, the item will be inserted into any slot.
* @return The number of transferred items.
* @throws LuaException If the peripheral to transfer to doesn't exist or isn't an inventory.
* @throws LuaException If either source or destination slot is out of range.
* @cc.see peripheral.getName Allows you to get the name of a [wrapped][`peripheral.wrap`] peripheral.
* @cc.usage Wrap two chests, and push an item from one to another.
* <pre>{@code
* local chest_a = peripheral.wrap("minecraft:chest_0")
* local chest_b = peripheral.wrap("minecraft:chest_1")
*
* chest_a.pullItems(peripheral.getName(chest_b), 1)
* }</pre>
*/
@LuaFunction(mainThread = true)
public abstract int pullItems(
T to, IComputerAccess computer, String fromName, int fromSlot, Optional<Integer> limit, Optional<Integer> toSlot
) throws LuaException;
}

View File

@ -11,6 +11,7 @@ import dan200.computercraft.api.turtle.TurtleCommandResult;
import dan200.computercraft.api.turtle.TurtleSide;
import dan200.computercraft.core.apis.IAPIEnvironment;
import dan200.computercraft.core.metrics.Metrics;
import dan200.computercraft.shared.peripheral.generic.methods.AbstractInventoryMethods;
import dan200.computercraft.shared.turtle.core.*;
import java.util.Optional;
@ -749,7 +750,7 @@ public class TurtleAPI implements ILuaAPI {
* -- count = 13,
* -- }
* }</pre>
* @see dan200.computercraft.shared.peripheral.generic.methods.InventoryMethods#getItemDetail Describes the information returned by a detailed query.
* @see AbstractInventoryMethods#getItemDetail Describes the information returned by a detailed query.
*/
@LuaFunction
public final MethodResult getItemDetail(ILuaContext context, Optional<Integer> slot, Optional<Boolean> detailed) throws LuaException {

View File

@ -171,7 +171,7 @@ final class Generator<T> {
// Instance methods must be final - this prevents them being overridden and potentially exposed twice.
var modifiers = method.getModifiers();
if (!Modifier.isStatic(modifiers) && !Modifier.isFinal(modifiers)) {
if (!Modifier.isStatic(modifiers) && !Modifier.isFinal(modifiers) && !Modifier.isFinal(method.getDeclaringClass().getModifiers())) {
LOG.warn("Lua Method {}.{} should be final.", method.getDeclaringClass().getName(), method.getName());
}

View File

@ -4,14 +4,11 @@
package dan200.computercraft.shared.peripheral.generic.methods;
import dan200.computercraft.api.ComputerCraftAPI;
import dan200.computercraft.api.detail.VanillaDetailRegistries;
import dan200.computercraft.api.lua.LuaException;
import dan200.computercraft.api.lua.LuaFunction;
import dan200.computercraft.api.peripheral.GenericPeripheral;
import dan200.computercraft.api.peripheral.IComputerAccess;
import dan200.computercraft.api.peripheral.IPeripheral;
import dan200.computercraft.api.peripheral.PeripheralType;
import dan200.computercraft.shared.platform.FabricContainerTransfer;
import net.fabricmc.fabric.api.transfer.v1.item.ItemStorage;
import net.fabricmc.fabric.api.transfer.v1.item.ItemVariant;
@ -32,37 +29,30 @@ import java.util.Optional;
import static dan200.computercraft.core.util.ArgumentHelpers.assertBetween;
/**
* Methods for interacting with inventories. This mirrors the Forge version.
* Inventory methods for Fabric's {@link SlottedStorage} and {@link ItemVariant}s.
* <p>
* The generic peripheral system doesn't (currently) support generics, and so we need to wrap this in a
* {@link StorageWrapper} box.
*/
@SuppressWarnings("UnstableApiUsage")
public class InventoryMethods implements GenericPeripheral {
@Override
public PeripheralType getType() {
return PeripheralType.ofAdditional("inventory");
}
@Override
public String id() {
return ComputerCraftAPI.MOD_ID + ":inventory";
}
public final class InventoryMethods extends AbstractInventoryMethods<InventoryMethods.StorageWrapper> {
/**
* Wrapper over a {@link SlottedStorage}.
* <p>
* The generic peripheral system doesn't (currently) support generics, and so we need put the inventory in a box.
*
* @param storage The underlying storage
*/
public record StorageWrapper(SlottedStorage<ItemVariant> storage) {
}
@Override
@LuaFunction(mainThread = true)
public static int size(StorageWrapper inventory) {
public int size(StorageWrapper inventory) {
return inventory.storage().getSlots().size();
}
@Override
@LuaFunction(mainThread = true)
public static Map<Integer, Map<String, ?>> list(StorageWrapper inventory) {
public Map<Integer, Map<String, ?>> list(StorageWrapper inventory) {
Map<Integer, Map<String, ?>> result = new HashMap<>();
var slots = inventory.storage().getSlots();
var size = slots.size();
@ -74,23 +64,26 @@ public class InventoryMethods implements GenericPeripheral {
return result;
}
@Override
@Nullable
@LuaFunction(mainThread = true)
public static Map<String, ?> getItemDetail(StorageWrapper inventory, int slot) throws LuaException {
public Map<String, ?> getItemDetail(StorageWrapper inventory, int slot) throws LuaException {
assertBetween(slot, 1, inventory.storage().getSlotCount(), "Slot out of range (%s)");
var stack = toStack(inventory.storage().getSlot(slot - 1));
return stack.isEmpty() ? null : VanillaDetailRegistries.ITEM_STACK.getDetails(stack);
}
@Override
@LuaFunction(mainThread = true)
public static long getItemLimit(StorageWrapper inventory, int slot) throws LuaException {
public long getItemLimit(StorageWrapper inventory, int slot) throws LuaException {
assertBetween(slot, 1, inventory.storage().getSlotCount(), "Slot out of range (%s)");
return inventory.storage().getSlot(slot - 1).getCapacity();
}
@Override
@LuaFunction(mainThread = true)
public static int pushItems(
public int pushItems(
StorageWrapper from, IComputerAccess computer,
String toName, int fromSlot, Optional<Integer> limit, Optional<Integer> toSlot
) throws LuaException {
@ -112,8 +105,9 @@ public class InventoryMethods implements GenericPeripheral {
return moveItem(fromStorage, fromSlot - 1, to, toSlot.orElse(0) - 1, actualLimit);
}
@Override
@LuaFunction(mainThread = true)
public static int pullItems(
public int pullItems(
StorageWrapper to, IComputerAccess computer,
String fromName, int fromSlot, Optional<Integer> limit, Optional<Integer> toSlot
) throws LuaException {

View File

@ -9,7 +9,6 @@ plugins {
id("cc-tweaked.forge")
id("cc-tweaked.gametest")
alias(libs.plugins.mixinGradle)
id("cc-tweaked.illuaminate")
id("cc-tweaked.mod-publishing")
}
@ -117,7 +116,6 @@ mixin {
}
configurations {
register("cctJavadoc")
minecraftLibrary { extendsFrom(minecraftEmbed.get()) }
}
@ -167,40 +165,10 @@ dependencies {
testModImplementation(testFixtures(project(":forge")))
testFixturesImplementation(testFixtures(project(":core")))
"cctJavadoc"(libs.cctJavadoc)
}
illuaminate {
version.set(libs.versions.illuaminate)
}
// Compile tasks
val luaJavadoc by tasks.registering(Javadoc::class) {
description = "Generates documentation for Java-side Lua functions."
group = JavaBasePlugin.DOCUMENTATION_GROUP
source(sourceSets.main.get().java)
source(project(":core").sourceSets.main.get().java)
source(project(":common").sourceSets.main.get().java)
destinationDir = layout.buildDirectory.dir("docs/luaJavadoc").get().asFile
classpath = sourceSets.main.get().compileClasspath
val options = options as StandardJavadocDocletOptions
options.docletpath = configurations["cctJavadoc"].files.toList()
options.doclet = "cc.tweaked.javadoc.LuaDoclet"
options.addStringOption("project-root", rootProject.file(".").absolutePath)
options.noTimestamp(false)
javadocTool.set(
javaToolchains.javadocToolFor {
languageVersion.set(cc.tweaked.gradle.CCTweakedPlugin.JAVA_VERSION)
},
)
}
tasks.processResources {
inputs.property("modVersion", modVersion)
inputs.property("forgeVersion", libs.versions.forge.get())
@ -240,24 +208,6 @@ tasks.test {
systemProperty("cct.test-files", layout.buildDirectory.dir("tmp/testFiles").getAbsolutePath())
}
val lintLua by tasks.registering(IlluaminateExec::class) {
group = JavaBasePlugin.VERIFICATION_GROUP
description = "Lint Lua (and Lua docs) with illuaminate"
// Config files
inputs.file(rootProject.file("illuaminate.sexp")).withPropertyName("illuaminate.sexp")
// Sources
inputs.files(rootProject.fileTree("doc")).withPropertyName("docs")
inputs.files(project(":core").fileTree("src/main/resources/data/computercraft/lua")).withPropertyName("lua rom")
inputs.files(luaJavadoc)
args = listOf("lint")
workingDir = rootProject.projectDir
doFirst { if (System.getenv("GITHUB_ACTIONS") != null) println("::add-matcher::.github/matchers/illuaminate.json") }
doLast { if (System.getenv("GITHUB_ACTIONS") != null) println("::remove-matcher owner=illuaminate::") }
}
val runGametest by tasks.registering(JavaExec::class) {
group = LifecycleBasePlugin.VERIFICATION_GROUP
description = "Runs tests on a temporary Minecraft instance."

View File

@ -4,54 +4,22 @@
package dan200.computercraft.shared.peripheral.generic.methods;
import dan200.computercraft.api.ComputerCraftAPI;
import dan200.computercraft.api.lua.LuaFunction;
import dan200.computercraft.api.peripheral.GenericPeripheral;
import dan200.computercraft.api.peripheral.PeripheralType;
import net.minecraftforge.energy.IEnergyStorage;
/**
* Methods for interacting with blocks using Forge's energy storage system.
* <p>
* This works with energy storage blocks, as well as generators and machines which consume energy.
* <p>
* > [!NOTE]
* > Due to limitations with Forge's energy API, it is not possible to measure throughput (i.e. RF
* > used/generated per tick).
*
* @cc.module energy_storage
* @cc.since 1.94.0
* Fluid methods for Forge's {@link IEnergyStorage}.
*/
public class EnergyMethods implements GenericPeripheral {
public final class EnergyMethods extends AbstractEnergyMethods<IEnergyStorage> {
@Override
public PeripheralType getType() {
return PeripheralType.ofAdditional("energy_storage");
}
@Override
public String id() {
return ComputerCraftAPI.MOD_ID + ":energy";
}
/**
* Get the energy of this block.
*
* @param energy The current energy storage.
* @return The energy stored in this block, in FE.
*/
@LuaFunction(mainThread = true)
public static int getEnergy(IEnergyStorage energy) {
public int getEnergy(IEnergyStorage energy) {
return energy.getEnergyStored();
}
/**
* Get the maximum amount of energy this block can store.
*
* @param energy The current energy storage.
* @return The energy capacity of this block.
*/
@Override
@LuaFunction(mainThread = true)
public static int getEnergyCapacity(IEnergyStorage energy) {
public int getEnergyCapacity(IEnergyStorage energy) {
return energy.getMaxEnergyStored();
}
}

View File

@ -4,13 +4,10 @@
package dan200.computercraft.shared.peripheral.generic.methods;
import dan200.computercraft.api.ComputerCraftAPI;
import dan200.computercraft.api.detail.ForgeDetailRegistries;
import dan200.computercraft.api.lua.LuaException;
import dan200.computercraft.api.lua.LuaFunction;
import dan200.computercraft.api.peripheral.GenericPeripheral;
import dan200.computercraft.api.peripheral.IComputerAccess;
import dan200.computercraft.api.peripheral.PeripheralType;
import dan200.computercraft.shared.platform.RegistryWrappers;
import net.minecraft.world.level.block.entity.BlockEntity;
import net.minecraftforge.common.capabilities.ForgeCapabilities;
@ -26,37 +23,12 @@ import java.util.Optional;
import static dan200.computercraft.shared.util.ArgumentHelpers.getRegistryEntry;
/**
* Methods for interacting with tanks and other fluid storage blocks.
*
* @cc.module fluid_storage
* @cc.since 1.94.0
* Fluid methods for Forge's {@link IFluidHandler}.
*/
public class FluidMethods implements GenericPeripheral {
public final class FluidMethods extends AbstractFluidMethods<IFluidHandler> {
@Override
public PeripheralType getType() {
return PeripheralType.ofAdditional("fluid_storage");
}
@Override
public String id() {
return ComputerCraftAPI.MOD_ID + ":fluid";
}
/**
* Get all "tanks" in this fluid storage.
* <p>
* Each tank either contains some amount of fluid or is empty. Tanks with fluids inside will return some basic
* information about the fluid, including its name and amount.
* <p>
* The returned table is sparse, and so empty tanks will be `nil` - it is recommended to loop over using `pairs`
* rather than `ipairs`.
*
* @param fluids The current fluid handler.
* @return All tanks.
* @cc.treturn { (table|nil)... } All tanks in this fluid storage.
*/
@LuaFunction(mainThread = true)
public static Map<Integer, Map<String, ?>> tanks(IFluidHandler fluids) {
public Map<Integer, Map<String, ?>> tanks(IFluidHandler fluids) {
Map<Integer, Map<String, ?>> result = new HashMap<>();
var size = fluids.getTanks();
for (var i = 0; i < size; i++) {
@ -67,24 +39,9 @@ public class FluidMethods implements GenericPeripheral {
return result;
}
/**
* Move a fluid from one fluid container to another connected one.
* <p>
* This allows you to pull fluid in the current fluid container to another container <em>on the same wired
* network</em>. Both containers must attached to wired modems which are connected via a cable.
*
* @param from Container to move fluid from.
* @param computer The current computer.
* @param toName The name of the peripheral/container to push to. This is the string given to [`peripheral.wrap`],
* and displayed by the wired modem.
* @param limit The maximum amount of fluid to move.
* @param fluidName The fluid to move. If not given, an arbitrary fluid will be chosen.
* @return The amount of moved fluid.
* @throws LuaException If the peripheral to transfer to doesn't exist or isn't an fluid container.
* @cc.see peripheral.getName Allows you to get the name of a [wrapped][`peripheral.wrap`] peripheral.
*/
@Override
@LuaFunction(mainThread = true)
public static int pushFluid(
public int pushFluid(
IFluidHandler from, IComputerAccess computer,
String toName, Optional<Integer> limit, Optional<String> fluidName
) throws LuaException {
@ -107,24 +64,9 @@ public class FluidMethods implements GenericPeripheral {
: moveFluid(from, new FluidStack(fluid, actualLimit), to);
}
/**
* Move a fluid from a connected fluid container into this oneone.
* <p>
* This allows you to pull fluid in the current fluid container from another container <em>on the same wired
* network</em>. Both containers must attached to wired modems which are connected via a cable.
*
* @param to Container to move fluid to.
* @param computer The current computer.
* @param fromName The name of the peripheral/container to push to. This is the string given to [`peripheral.wrap`],
* and displayed by the wired modem.
* @param limit The maximum amount of fluid to move.
* @param fluidName The fluid to move. If not given, an arbitrary fluid will be chosen.
* @return The amount of moved fluid.
* @throws LuaException If the peripheral to transfer to doesn't exist or isn't an fluid container.
* @cc.see peripheral.getName Allows you to get the name of a [wrapped][`peripheral.wrap`] peripheral.
*/
@Override
@LuaFunction(mainThread = true)
public static int pullFluid(
public int pullFluid(
IFluidHandler to, IComputerAccess computer,
String fromName, Optional<Integer> limit, Optional<String> fluidName
) throws LuaException {
@ -194,7 +136,7 @@ public class FluidMethods implements GenericPeripheral {
* @return The amount of fluid moved.
*/
private static int moveFluid(IFluidHandler from, FluidStack extracted, int limit, IFluidHandler to) {
if (extracted == null || extracted.getAmount() <= 0) return 0;
if (extracted.getAmount() <= 0) return 0;
// Limit the amount to extract.
extracted = extracted.copy();

View File

@ -4,14 +4,10 @@
package dan200.computercraft.shared.peripheral.generic.methods;
import dan200.computercraft.api.ComputerCraftAPI;
import dan200.computercraft.api.detail.VanillaDetailRegistries;
import dan200.computercraft.api.lua.ILuaContext;
import dan200.computercraft.api.lua.LuaException;
import dan200.computercraft.api.lua.LuaFunction;
import dan200.computercraft.api.peripheral.GenericPeripheral;
import dan200.computercraft.api.peripheral.IComputerAccess;
import dan200.computercraft.api.peripheral.PeripheralType;
import dan200.computercraft.shared.platform.ForgeContainerTransfer;
import net.minecraft.world.Container;
import net.minecraft.world.level.block.entity.BlockEntity;
@ -28,59 +24,18 @@ import java.util.Optional;
import static dan200.computercraft.core.util.ArgumentHelpers.assertBetween;
/**
* Methods for interacting with inventories.
*
* @cc.module inventory
* @cc.since 1.94.0
* Inventory methods for Forge's {@link IItemHandler}.
*/
public class InventoryMethods implements GenericPeripheral {
public final class InventoryMethods extends AbstractInventoryMethods<IItemHandler> {
@Override
public PeripheralType getType() {
return PeripheralType.ofAdditional("inventory");
}
@Override
public String id() {
return ComputerCraftAPI.MOD_ID + ":inventory";
}
/**
* Get the size of this inventory.
*
* @param inventory The current inventory.
* @return The number of slots in this inventory.
*/
@LuaFunction(mainThread = true)
public static int size(IItemHandler inventory) {
public int size(IItemHandler inventory) {
return inventory.getSlots();
}
/**
* List all items in this inventory. This returns a table, with an entry for each slot.
* <p>
* Each item in the inventory is represented by a table containing some basic information, much like
* {@link dan200.computercraft.shared.turtle.apis.TurtleAPI#getItemDetail(ILuaContext, Optional, Optional)}
* includes. More information can be fetched with {@link #getItemDetail}. The table contains the item `name`, the
* `count` and an a (potentially nil) hash of the item's `nbt.` This NBT data doesn't contain anything useful, but
* allows you to distinguish identical items.
* <p>
* The returned table is sparse, and so empty slots will be `nil` - it is recommended to loop over using `pairs`
* rather than `ipairs`.
*
* @param inventory The current inventory.
* @return All items in this inventory.
* @cc.treturn { (table|nil)... } All items in this inventory.
* @cc.usage Find an adjacent chest and print all items in it.
*
* <pre>{@code
* local chest = peripheral.find("minecraft:chest")
* for slot, item in pairs(chest.list()) do
* print(("%d x %s in slot %d"):format(item.count, item.name, slot))
* end
* }</pre>
*/
@Override
@LuaFunction(mainThread = true)
public static Map<Integer, Map<String, ?>> list(IItemHandler inventory) {
public Map<Integer, Map<String, ?>> list(IItemHandler inventory) {
Map<Integer, Map<String, ?>> result = new HashMap<>();
var size = inventory.getSlots();
for (var i = 0; i < size; i++) {
@ -91,106 +46,26 @@ public class InventoryMethods implements GenericPeripheral {
return result;
}
/**
* Get detailed information about an item.
* <p>
* The returned information contains the same information as each item in
* {@link #list}, as well as additional details like the display name
* (`displayName`), and item and item durability (`damage`, `maxDamage`, `durability`).
* <p>
* Some items include more information (such as enchantments) - it is
* recommended to print it out using [`textutils.serialize`] or in the Lua
* REPL, to explore what is available.
* <p>
* > [Deprecated fields][!INFO]
* > Older versions of CC: Tweaked exposed an {@code itemGroups} field, listing the
* > creative tabs an item was available under. This information is no longer available on
* > more recent versions of the game, and so this field will always be empty. Do not use this
* > field in new code!
*
* @param inventory The current inventory.
* @param slot The slot to get information about.
* @return Information about the item in this slot, or {@code nil} if not present.
* @throws LuaException If the slot is out of range.
* @cc.treturn table Information about the item in this slot, or {@code nil} if not present.
* @cc.usage Print some information about the first in a chest.
*
* <pre>{@code
* local chest = peripheral.find("minecraft:chest")
* local item = chest.getItemDetail(1)
* if not item then print("No item") return end
*
* print(("%s (%s)"):format(item.displayName, item.name))
* print(("Count: %d/%d"):format(item.count, item.maxCount))
*
* if item.damage then
* print(("Damage: %d/%d"):format(item.damage, item.maxDamage))
* end
* }</pre>
*/
@Override
@Nullable
@LuaFunction(mainThread = true)
public static Map<String, ?> getItemDetail(IItemHandler inventory, int slot) throws LuaException {
public Map<String, ?> getItemDetail(IItemHandler inventory, int slot) throws LuaException {
assertBetween(slot, 1, inventory.getSlots(), "Slot out of range (%s)");
var stack = inventory.getStackInSlot(slot - 1);
return stack.isEmpty() ? null : VanillaDetailRegistries.ITEM_STACK.getDetails(stack);
}
/**
* Get the maximum number of items which can be stored in this slot.
* <p>
* Typically this will be limited to 64 items. However, some inventories (such as barrels or caches) can store
* hundreds or thousands of items in one slot.
*
* @param inventory Inventory to probe.
* @param slot The slot
* @return The maximum number of items in this slot.
* @throws LuaException If the slot is out of range.
* @cc.usage Count the maximum number of items an adjacent chest can hold.
* <pre>{@code
* local chest = peripheral.find("minecraft:chest")
* local total = 0
* for i = 1, chest.size() do
* total = total + chest.getItemLimit(i)
* end
* print(total)
* }</pre>
* @cc.since 1.96.0
*/
@Override
@LuaFunction(mainThread = true)
public static int getItemLimit(IItemHandler inventory, int slot) throws LuaException {
public long getItemLimit(IItemHandler inventory, int slot) throws LuaException {
assertBetween(slot, 1, inventory.getSlots(), "Slot out of range (%s)");
return inventory.getSlotLimit(slot - 1);
}
/**
* Push items from one inventory to another connected one.
* <p>
* This allows you to push an item in an inventory to another inventory <em>on the same wired network</em>. Both
* inventories must attached to wired modems which are connected via a cable.
*
* @param from Inventory to move items from.
* @param computer The current computer.
* @param toName The name of the peripheral/inventory to push to. This is the string given to [`peripheral.wrap`],
* and displayed by the wired modem.
* @param fromSlot The slot in the current inventory to move items to.
* @param limit The maximum number of items to move. Defaults to the current stack limit.
* @param toSlot The slot in the target inventory to move to. If not given, the item will be inserted into any slot.
* @return The number of transferred items.
* @throws LuaException If the peripheral to transfer to doesn't exist or isn't an inventory.
* @throws LuaException If either source or destination slot is out of range.
* @cc.see peripheral.getName Allows you to get the name of a [wrapped][`peripheral.wrap`] peripheral.
* @cc.usage Wrap two chests, and push an item from one to another.
* <pre>{@code
* local chest_a = peripheral.wrap("minecraft:chest_0")
* local chest_b = peripheral.wrap("minecraft:chest_1")
*
* chest_a.pushItems(peripheral.getName(chest_b), 1)
* }</pre>
*/
@Override
@LuaFunction(mainThread = true)
public static int pushItems(
public int pushItems(
IItemHandler from, IComputerAccess computer,
String toName, int fromSlot, Optional<Integer> limit, Optional<Integer> toSlot
) throws LuaException {
@ -210,33 +85,9 @@ public class InventoryMethods implements GenericPeripheral {
return moveItem(from, fromSlot - 1, to, toSlot.orElse(0) - 1, actualLimit);
}
/**
* Pull items from a connected inventory into this one.
* <p>
* This allows you to transfer items between inventories <em>on the same wired network</em>. Both this and the source
* inventory must attached to wired modems which are connected via a cable.
*
* @param to Inventory to move items to.
* @param computer The current computer.
* @param fromName The name of the peripheral/inventory to pull from. This is the string given to [`peripheral.wrap`],
* and displayed by the wired modem.
* @param fromSlot The slot in the source inventory to move items from.
* @param limit The maximum number of items to move. Defaults to the current stack limit.
* @param toSlot The slot in current inventory to move to. If not given, the item will be inserted into any slot.
* @return The number of transferred items.
* @throws LuaException If the peripheral to transfer to doesn't exist or isn't an inventory.
* @throws LuaException If either source or destination slot is out of range.
* @cc.see peripheral.getName Allows you to get the name of a [wrapped][`peripheral.wrap`] peripheral.
* @cc.usage Wrap two chests, and push an item from one to another.
* <pre>{@code
* local chest_a = peripheral.wrap("minecraft:chest_0")
* local chest_b = peripheral.wrap("minecraft:chest_1")
*
* chest_a.pullItems(peripheral.getName(chest_b), 1)
* }</pre>
*/
@Override
@LuaFunction(mainThread = true)
public static int pullItems(
public int pullItems(
IItemHandler to, IComputerAccess computer,
String fromName, int fromSlot, Optional<Integer> limit, Optional<Integer> toSlot
) throws LuaException {

View File

@ -94,7 +94,7 @@ val illuaminateDocs by tasks.registering(cc.tweaked.gradle.IlluaminateExecToDir:
// Sources
inputs.files(rootProject.fileTree("doc")).withPropertyName("docs")
inputs.files(project(":core").fileTree("src/main/resources/data/computercraft/lua")).withPropertyName("lua rom")
inputs.files(project(":forge").tasks.named("luaJavadoc"))
inputs.files(project(":common").tasks.named("luaJavadoc"))
// Assets
inputs.files(rollup)

View File

@ -87,21 +87,16 @@ public final class StaticGenerator<T> {
var name = method.getDeclaringClass().getName() + "." + method.getName();
var modifiers = method.getModifiers();
// Instance methods must be final - this prevents them being overridden and potentially exposed twice.
if (!Modifier.isStatic(modifiers) && !Modifier.isFinal(modifiers)) {
// Methods must be final - this prevents them being overridden and potentially exposed twice.
if (!Modifier.isFinal(modifiers) && !Modifier.isFinal(method.getDeclaringClass().getModifiers())) {
System.err.printf("Lua Method %s should be final.\n", name);
}
if (!Modifier.isPublic(modifiers)) {
if (!Modifier.isPublic(modifiers) || !Modifier.isPublic(method.getDeclaringClass().getModifiers())) {
System.err.printf("Lua Method %s should be a public method.\n", name);
return Optional.empty();
}
if (!Modifier.isPublic(method.getDeclaringClass().getModifiers())) {
System.err.printf("Lua Method %s should be on a public class.\n", name);
return Optional.empty();
}
var exceptions = method.getExceptionTypes();
for (var exception : exceptions) {
if (exception != LuaException.class) {
@ -116,11 +111,8 @@ public final class StaticGenerator<T> {
return Optional.empty();
}
// We have some rather ugly handling of static methods in both here and the main generate function. Static methods
// only come from generic sources, so this should be safe.
var target = Modifier.isStatic(modifiers) ? method.getParameterTypes()[0] : method.getDeclaringClass();
try {
var target = method.getDeclaringClass();
var bytes = generate(classPrefix + method.getDeclaringClass().getSimpleName() + "$" + method.getName(), target, method, annotation.unsafe());
if (bytes == null) return Optional.empty();
@ -170,11 +162,9 @@ public final class StaticGenerator<T> {
var mw = cw.visitMethod(ACC_PUBLIC, METHOD_NAME, methodDesc, null, EXCEPTIONS);
mw.visitCode();
// If we're an instance method, load the target as the first argument.
if (!Modifier.isStatic(targetMethod.getModifiers())) {
mw.visitVarInsn(ALOAD, 1);
mw.visitTypeInsn(CHECKCAST, Type.getInternalName(target));
}
// Load the target as the first argument.
mw.visitVarInsn(ALOAD, 1);
mw.visitTypeInsn(CHECKCAST, Type.getInternalName(target));
var argIndex = 0;
for (var genericArg : targetMethod.getGenericParameterTypes()) {
@ -184,7 +174,7 @@ public final class StaticGenerator<T> {
}
mw.visitMethodInsn(
Modifier.isStatic(targetMethod.getModifiers()) ? INVOKESTATIC : INVOKEVIRTUAL,
INVOKEVIRTUAL,
Type.getInternalName(targetMethod.getDeclaringClass()), targetMethod.getName(),
Type.getMethodDescriptor(targetMethod), false
);
@ -244,12 +234,10 @@ public final class StaticGenerator<T> {
if (klass == null) return null;
if (klass == String.class) {
mw.visitTypeInsn(NEW, INTERNAL_COERCED);
mw.visitInsn(DUP);
mw.visitVarInsn(ALOAD, 2 + context.size());
loadInt(mw, argIndex);
mw.visitMethodInsn(INVOKEINTERFACE, INTERNAL_ARGUMENTS, "getStringCoerced", "(I)Ljava/lang/String;", true);
mw.visitMethodInsn(INVOKESPECIAL, INTERNAL_COERCED, "<init>", "(Ljava/lang/Object;)V", false);
loadCoerced(mw, argIndex, "getStringCoerced", "(I)Ljava/lang/String;");
return true;
} else if (klass == ByteBuffer.class) {
loadCoerced(mw, argIndex, "getBytesCoerced", "(I)Ljava/nio/ByteBuffer;");
return true;
}
}
@ -324,6 +312,15 @@ public final class StaticGenerator<T> {
}
}
private void loadCoerced(MethodVisitor mw, int argIndex, String getter, String getterType) {
mw.visitTypeInsn(NEW, INTERNAL_COERCED);
mw.visitInsn(DUP);
mw.visitVarInsn(ALOAD, 2 + context.size());
loadInt(mw, argIndex);
mw.visitMethodInsn(INVOKEINTERFACE, INTERNAL_ARGUMENTS, getter, getterType, true);
mw.visitMethodInsn(INVOKESPECIAL, INTERNAL_COERCED, "<init>", "(Ljava/lang/Object;)V", false);
}
@SuppressWarnings("Guava")
static <T, U> com.google.common.base.Function<T, U> catching(Function<T, U> function, U def) {
return x -> {