1
0
mirror of https://github.com/SquidDev-CC/CC-Tweaked synced 2025-04-12 05:43:20 +00:00

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

Oh, I'm sure I missed something here. This was a nasty merge, has the
docs have changed so much in each version.
This commit is contained in:
Jonathan Coates 2025-01-11 17:53:53 +00:00
commit 7337b91692
No known key found for this signature in database
GPG Key ID: B9E431FF07C98D06
61 changed files with 1009 additions and 491 deletions

View File

@ -58,6 +58,7 @@ repos:
exclude: |
(?x)^(
projects/[a-z]+/src/generated|
projects/[a-z]+/src/examples/generatedResources|
projects/core/src/test/resources/test-rom/data/json-parsing/|
.*\.dfpwm
)

View File

@ -22,8 +22,7 @@ If you have a bug, suggestion, or other feedback, the best thing to do is [file
use the issue templates - they provide a useful hint on what information to provide.
## Translations
Translations are managed through [CrowdIn], an online interface for managing language strings. Translations may either
be contributed there, or directly via a pull request.
Translations are managed through [CrowdIn], an online interface for managing language strings.
## Setting up a development environment
In order to develop CC: Tweaked, you'll need to download the source code and then run it.
@ -49,9 +48,12 @@ If you want to run CC:T in a normal Minecraft instance, run `./gradlew assemble`
`projects/forge/build/libs` (for Forge) or `projects/fabric/build/libs` (for Fabric).
## Developing CC: Tweaked
Before making any major changes to CC: Tweaked, I'd recommend you have a read of the [the architecture
document][architecture] first. While it's not a comprehensive document, it gives a good hint of where you should start
looking to make your changes. As always, if you're not sure, [do ask the community][community]!
Before making any major changes to CC: Tweaked, I'd recommend starting opening an issue or starting a discussion on
GitHub first. It's often helpful to discuss features before spending time developing them!
Once you're ready to start programming, have a read of the [the architecture document][architecture] first. While it's
not a comprehensive document, it gives a good hint of where you should start looking to make your changes. As always, if
you're not sure, [do ask the community][community]!
### Testing
When making larger changes, it may be useful to write a test to make sure your code works as expected.

View File

@ -60,19 +60,6 @@ dependencies {
}
```
When using ForgeGradle, you may also need to add the following:
```groovy
minecraft {
runs {
configureEach {
property 'mixin.env.remapRefMap', 'true'
property 'mixin.env.refMapRemappingFile', "${buildDir}/createSrgToMcp/output.srg"
}
}
}
```
You should also be careful to only use classes within the `dan200.computercraft.api` package. Non-API classes are
subject to change at any point. If you depend on functionality outside the API (or need to mixin to CC:T), please file
an issue to let me know!

View File

@ -8,10 +8,10 @@ SPDX-PackageSupplier = "Jonathan Coates <git@squiddev.cc>"
SPDX-PackageDownloadLocation = "https://github.com/cc-tweaked/cc-tweaked"
[[annotations]]
# Generated/data files are CC0.
SPDX-FileCopyrightText = "The CC: Tweaked Developers"
SPDX-License-Identifier = "CC0-1.0"
path = [
# Generated/data files are CC0.
"gradle/gradle-daemon-jvm.properties",
"projects/common/src/main/resources/assets/computercraft/sounds.json",
"projects/common/src/main/resources/assets/computercraft/sounds/empty.ogg",
@ -20,6 +20,11 @@ path = [
"projects/**/src/generated/**",
"projects/web/src/htmlTransform/export/index.json",
"projects/web/src/htmlTransform/export/items/minecraft/**",
# GitHub build scripts are CC0. While we could add a header to each file,
# it's unclear if it will break actions or issue templates in some way.
".github/**",
# Example mod is CC0.
"projects/**/src/examples/**"
]
[[annotations]]
@ -74,7 +79,7 @@ path = [
]
[[annotations]]
# Community-contributed license files
# Community-contributed language files
SPDX-FileCopyrightText = "2017 The CC: Tweaked Developers"
SPDX-License-Identifier = "LicenseRef-CCPL"
path = [
@ -88,18 +93,11 @@ path = [
]
[[annotations]]
# Community-contributed license files
# Community-contributed language files
SPDX-FileCopyrightText = "2017 The CC: Tweaked Developers"
SPDX-License-Identifier = "MPL-2.0"
path = "projects/common/src/main/resources/assets/computercraft/lang/**"
[[annotations]]
# GitHub build scripts are CC0. While we could add a header to each file,
# it's unclear if it will break actions or issue templates in some way.
SPDX-FileCopyrightText = "Jonathan Coates <git@squiddev.cc>"
SPDX-License-Identifier = "CC0-1.0"
path = ".github/**"
[[annotations]]
path = ["gradle/wrapper/**"]
SPDX-FileCopyrightText = "Gradle Inc"

View File

@ -14,14 +14,10 @@ repositories {
mavenCentral()
gradlePluginPortal()
maven("https://maven.neoforged.net/releases") {
maven("https://maven.neoforged.net") {
name = "NeoForge"
content {
includeGroup("net.minecraftforge")
includeGroup("net.neoforged")
includeGroup("net.neoforged.gradle")
includeModule("codechicken", "DiffPatch")
includeModule("net.covers1624", "Quack")
}
}
@ -48,7 +44,7 @@ dependencies {
implementation(libs.fabric.loom)
implementation(libs.ideaExt)
implementation(libs.minotaur)
implementation(libs.neoGradle.userdev)
implementation(libs.modDevGradle)
implementation(libs.vanillaExtract)
}

View File

@ -11,20 +11,18 @@ import cc.tweaked.gradle.MinecraftConfigurations
plugins {
id("cc-tweaked.java-convention")
id("net.neoforged.gradle.userdev")
id("net.neoforged.moddev")
}
plugins.apply(CCTweakedPlugin::class.java)
val mcVersion: String by extra
minecraft {
modIdentifier("computercraft")
}
neoForge {
val libs = project.extensions.getByType<VersionCatalogsExtension>().named("libs")
version = libs.findVersion("neoForge").get().toString()
subsystems {
parchment {
val libs = project.extensions.getByType<VersionCatalogsExtension>().named("libs")
minecraftVersion = libs.findVersion("parchmentMc").get().toString()
mappingsVersion = libs.findVersion("parchment").get().toString()
}

View File

@ -99,6 +99,7 @@ sourceSets.all {
check("OperatorPrecedence", CheckSeverity.OFF) // For now.
check("NonOverridingEquals", CheckSeverity.OFF) // Peripheral.equals makes this hard to avoid
check("FutureReturnValueIgnored", CheckSeverity.OFF) // Too many false positives with Netty
check("InvalidInlineTag", CheckSeverity.OFF) // Triggered by @snippet. Can be removed on Java 21.
check("NullAway", CheckSeverity.ERROR)
option(

View File

@ -2,15 +2,16 @@
//
// SPDX-License-Identifier: MPL-2.0
import cc.tweaked.gradle.clientClasses
import cc.tweaked.gradle.commonClasses
/**
* Sets up the configurations for writing game tests.
*
* See notes in [cc.tweaked.gradle.MinecraftConfigurations] for the general design behind these cursed ideas.
*/
import cc.tweaked.gradle.MinecraftConfigurations
import cc.tweaked.gradle.clientClasses
import cc.tweaked.gradle.commonClasses
plugins {
id("cc-tweaked.kotlin-convention")
id("cc-tweaked.java-convention")
@ -19,33 +20,16 @@ plugins {
val main = sourceSets["main"]
val client = sourceSets["client"]
// datagen and testMod inherit from the main and client classpath, just so we have access to Minecraft classes.
val datagen by sourceSets.creating {
compileClasspath += main.compileClasspath + client.compileClasspath
runtimeClasspath += main.runtimeClasspath + client.runtimeClasspath
}
MinecraftConfigurations.createDerivedConfiguration(project, MinecraftConfigurations.DATAGEN)
MinecraftConfigurations.createDerivedConfiguration(project, MinecraftConfigurations.EXAMPLES)
MinecraftConfigurations.createDerivedConfiguration(project, MinecraftConfigurations.TEST_MOD)
val testMod by sourceSets.creating {
compileClasspath += main.compileClasspath + client.compileClasspath
runtimeClasspath += main.runtimeClasspath + client.runtimeClasspath
}
// Set up generated resources
sourceSets.main { resources.srcDir("src/generated/resources") }
sourceSets.named("examples") { resources.srcDir("src/examples/generatedResources") }
val extraConfigurations = listOf(datagen, testMod)
configurations {
for (config in extraConfigurations) {
named(config.compileClasspathConfigurationName) { shouldResolveConsistentlyWith(compileClasspath.get()) }
named(config.runtimeClasspathConfigurationName) { shouldResolveConsistentlyWith(runtimeClasspath.get()) }
}
}
// Like the main test configurations, we're safe to depend on source set outputs.
dependencies {
for (config in extraConfigurations) {
add(config.implementationConfigurationName, main.output)
add(config.implementationConfigurationName, client.output)
}
}
// Make sure our examples compile.
tasks.check { dependsOn(tasks.named("compileExamplesJava")) }
// Similar to java-test-fixtures, but tries to avoid putting the obfuscated jar on the classpath.

View File

@ -113,7 +113,7 @@ abstract class CCTweakedExtension(
// Pull in sources from the other project.
extendSourceSet(otherProject, main)
extendSourceSet(otherProject, client)
for (sourceSet in listOf("datagen", "testMod", "testFixtures")) {
for (sourceSet in listOf(MinecraftConfigurations.DATAGEN, MinecraftConfigurations.EXAMPLES, MinecraftConfigurations.TEST_MOD, "testFixtures")) {
otherJava.sourceSets.findByName(sourceSet)?.let { extendSourceSet(otherProject, it) }
}

View File

@ -24,7 +24,6 @@ class MinecraftConfigurations private constructor(private val project: Project)
private val java = project.extensions.getByType(JavaPluginExtension::class.java)
private val sourceSets = java.sourceSets
private val configurations = project.configurations
private val objects = project.objects
private val main = sourceSets[SourceSet.MAIN_SOURCE_SET_NAME]
private val test = sourceSets[SourceSet.TEST_SOURCE_SET_NAME]
@ -37,13 +36,7 @@ class MinecraftConfigurations private constructor(private val project: Project)
val client = sourceSets.maybeCreate("client")
// Ensure the client classpaths behave the same as the main ones.
configurations.named(client.compileClasspathConfigurationName) {
shouldResolveConsistentlyWith(configurations[main.compileClasspathConfigurationName])
}
configurations.named(client.runtimeClasspathConfigurationName) {
shouldResolveConsistentlyWith(configurations[main.runtimeClasspathConfigurationName])
}
consistentWithMain(client)
// Set up an API configuration for clients (to ensure it's consistent with the main source set).
val clientApi = configurations.maybeCreate(client.apiConfigurationName).apply {
@ -85,6 +78,16 @@ class MinecraftConfigurations private constructor(private val project: Project)
setupBasic()
}
private fun consistentWithMain(sourceSet: SourceSet) {
configurations.named(sourceSet.compileClasspathConfigurationName) {
shouldResolveConsistentlyWith(configurations[main.compileClasspathConfigurationName])
}
configurations.named(sourceSet.runtimeClasspathConfigurationName) {
shouldResolveConsistentlyWith(configurations[main.runtimeClasspathConfigurationName])
}
}
private fun setupBasic() {
val client = sourceSets["client"]
@ -102,7 +105,24 @@ class MinecraftConfigurations private constructor(private val project: Project)
project.tasks.named("check") { dependsOn(checkDependencyConsistency) }
}
/**
* Create a new configuration that pulls in the main and client classes from the mod.
*/
private fun createDerivedConfiguration(name: String) {
val client = sourceSets["client"]
val sourceSet = sourceSets.create(name)
sourceSet.compileClasspath += main.compileClasspath + client.compileClasspath
sourceSet.runtimeClasspath += main.runtimeClasspath + client.runtimeClasspath
consistentWithMain(sourceSet)
project.dependencies.add(sourceSet.implementationConfigurationName, main.output)
project.dependencies.add(sourceSet.implementationConfigurationName, client.output)
}
companion object {
const val DATAGEN = "datagen"
const val EXAMPLES = "examples"
const val TEST_MOD = "testMod"
fun setupBasic(project: Project) {
MinecraftConfigurations(project).setupBasic()
}
@ -110,6 +130,10 @@ class MinecraftConfigurations private constructor(private val project: Project)
fun setup(project: Project) {
MinecraftConfigurations(project).setup()
}
fun createDerivedConfiguration(project: Project, name: String) {
MinecraftConfigurations(project).createDerivedConfiguration(name)
}
}
}

View File

@ -4,6 +4,7 @@
package cc.tweaked.gradle
import net.neoforged.moddevgradle.internal.RunGameTask
import org.gradle.api.GradleException
import org.gradle.api.file.FileSystemOperations
import org.gradle.api.invocation.Gradle
@ -65,6 +66,22 @@ abstract class ClientJavaExec : JavaExec() {
setTestProperties()
}
fun copyFromForge(path: String) = copyFromForge(project.tasks.getByName(path, RunGameTask::class))
/**
* Set this task to run a given [RunGameTask].
*/
fun copyFromForge(task: RunGameTask) {
copyFrom(task)
// Eagerly evaluate the behaviour of RunGameTask.exec
environment.putAll(task.environmentProperty.get())
classpath(task.classpathProvider)
workingDir = task.gameDirectory.get().asFile
setTestProperties() // setRunConfig may clobber some properties, ensure everything is set.
}
/**
* Copy configuration from a task with the given name.
*/

View File

@ -133,7 +133,7 @@ SPDX-License-Identifier: MPL-2.0
</module>
<module name="MethodTypeParameterName" />
<module name="PackageName">
<property name="format" value="^(dan200\.computercraft|cc\.tweaked)(\.[a-z][a-z0-9]*)*" />
<property name="format" value="^(dan200\.computercraft|cc\.tweaked|com\.example\.examplemod)(\.[a-z][a-z0-9]*)*" />
</module>
<module name="ParameterName" />
<module name="StaticVariableName">

View File

@ -68,7 +68,7 @@ ideaExt = "1.1.7"
illuaminate = "0.1.0-74-gf1551d5"
lwjgl = "3.3.3"
minotaur = "2.8.7"
neoGradle = "7.0.170"
modDevGradle = "2.0.74"
nullAway = "0.10.25"
shadow = "8.3.1"
spotless = "6.23.3"
@ -154,8 +154,8 @@ fabric-loom = { module = "net.fabricmc:fabric-loom", version.ref = "fabric-loom"
ideaExt = { module = "gradle.plugin.org.jetbrains.gradle.plugin.idea-ext:gradle-idea-ext", version.ref = "ideaExt" }
kotlin-plugin = { module = "org.jetbrains.kotlin:kotlin-gradle-plugin", version.ref = "kotlin" }
minotaur = { module = "com.modrinth.minotaur:Minotaur", version.ref = "minotaur" }
neoGradle-userdev = { module = "net.neoforged.gradle:userdev", version.ref = "neoGradle" }
nullAway = { module = "com.uber.nullaway:nullaway", version.ref = "nullAway" }
modDevGradle = { module = "net.neoforged:moddev-gradle", version.ref = "modDevGradle" }
spotless = { module = "com.diffplug.spotless:spotless-plugin-gradle", version.ref = "spotless" }
teavm-classlib = { module = "org.teavm:teavm-classlib", version.ref = "teavm" }
teavm-core = { module = "org.teavm:teavm-core", version.ref = "teavm" }

View File

@ -18,13 +18,28 @@ dependencies {
api(project(":core-api"))
}
val javadocOverview by tasks.registering(Copy::class) {
from("src/overview.html")
into(layout.buildDirectory.dir(name))
expand(
mapOf(
"mcVersion" to mcVersion,
"modVersion" to version,
),
)
}
tasks.javadoc {
title = "CC: Tweaked $version Minecraft $mcVersion"
title = "CC: Tweaked $version for Minecraft $mcVersion"
include("dan200/computercraft/api/**/*.java")
options {
(this as StandardJavadocDocletOptions)
inputs.files(javadocOverview)
overview(javadocOverview.get().destinationDir.resolve("overview.html").absolutePath)
groups = mapOf(
"Common" to listOf(
"dan200.computercraft.api",
@ -47,6 +62,12 @@ tasks.javadoc {
<link href=" https://cdn.jsdelivr.net/npm/prismjs@1.29.0/themes/prism.min.css " rel="stylesheet">
""".trimIndent(),
)
val snippetSources = listOf(":common", ":fabric", ":forge").flatMap {
project(it).sourceSets["examples"].allSource.sourceDirectories
}
inputs.files(snippetSources)
addPathOption("-snippet-path").value = snippetSources
}
// Include the core-api in our javadoc export. This is wrong, but it means we can export a single javadoc dump.

View File

@ -21,7 +21,14 @@ import java.util.stream.Stream;
* <p>
* Use {@code dan200.computercraft.api.client.FabricComputerCraftAPIClient#registerTurtleUpgradeModeller} to register a
* modeller on Fabric and {@code dan200.computercraft.api.client.turtle.RegisterTurtleModellersEvent} to register one
* on Forge
* on Forge.
*
* <h2>Example</h2>
* <h3>Fabric</h3>
* {@snippet class=com.example.examplemod.FabricExampleModClient region=turtle_modellers}
*
* <h3>Forge</h3>
* {@snippet class=com.example.examplemod.FabricExampleModClient region=turtle_modellers}
*
* @param <T> The type of turtle upgrade this modeller applies to.
* @see RegisterTurtleUpgradeModeller For multi-loader registration support.

View File

@ -171,16 +171,9 @@ public final class ComputerCraftAPI {
* using {@link ILuaAPI#getModuleName()} to expose this library as a module instead of as a global.
* <p>
* This may be used with {@link IComputerSystem#getComponent(ComputerComponent)} to only attach APIs to specific
* computers. For example, one can add an additional API just to turtles with the following code:
* computers. For example, one can add a new API just to turtles with the following code:
*
* {@snippet lang="java":
* ComputerCraftAPI.registerAPIFactory(computer -> {
* // Read the turtle component.
* var turtle = computer.getComponent(ComputerComponents.TURTLE);
* // If present then add our API.
* return turtle == null ? null : new MyCustomTurtleApi(turtle);
* });
* }
* {@snippet class=com.example.examplemod.ExampleAPI region=register}
*
* @param factory The factory for your API subclass.
* @see ILuaAPIFactory

View File

@ -20,32 +20,10 @@ import javax.annotation.Nullable;
* A peripheral which can be equipped to the back side of a pocket computer.
* <p>
* Pocket upgrades are defined in two stages. First, one creates a {@link IPocketUpgrade} subclass and corresponding
* {@link UpgradeType} instance, which are then registered in a registry.
* {@link UpgradeType} instance, which are then registered in a Minecraft registry.
* <p>
* You then write a JSON file in your mod's {@literal data/} folder. This is then parsed when the world is loaded, and
* the upgrade automatically registered. It is recommended this is done via
* <a href="../upgrades/UpgradeType.html#datagen">data generators</a>.
*
* <h2>Example</h2>
* {@snippet lang="java" :
* // We use Forge's DeferredRegister to register our upgrade type. Fabric mods may register their type directly.
* static final DeferredRegister<UpgradeType<? extends IPocketUpgrade>> POCKET_UPGRADES = DeferredRegister.create(IPocketUpgrade.typeRegistry(), "my_mod");
*
* // Register a new upgrade upgrade type called "my_upgrade".
* public static final RegistryObject<UpgradeType<MyUpgrade>> MY_UPGRADE =
* POCKET_UPGRADES.register("my_upgrade", () -> UpgradeType.simple(new MyUpgrade()));
*
* // Then in your constructor
* POCKET_UPGRADES.register(bus);
* }
* <p>
* We can then define a new upgrade using JSON by placing the following in
* {@code data/<my_mod>/computercraft/pocket_upgrade/<my_upgrade_id>.json}.
* {@snippet lang="json" :
* {
* "type": "my_mod:my_upgrade"
* }
* }
* the upgrade registered internally.
*/
public interface IPocketUpgrade extends UpgradeBase {
ResourceKey<Registry<IPocketUpgrade>> REGISTRY = ResourceKey.createRegistryKey(ResourceLocation.fromNamespaceAndPath(ComputerCraftAPI.MOD_ID, "pocket_upgrade"));

View File

@ -11,47 +11,79 @@ import dan200.computercraft.api.upgrades.UpgradeType;
import dan200.computercraft.impl.ComputerCraftAPIService;
import net.minecraft.core.Direction;
import net.minecraft.core.Registry;
import net.minecraft.core.RegistrySetBuilder.PatchedRegistries;
import net.minecraft.core.component.DataComponentPatch;
import net.minecraft.resources.ResourceKey;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.world.item.Items;
import javax.annotation.Nullable;
import java.util.function.Function;
/**
* The primary interface for defining an update for Turtles. A turtle update can either be a new tool, or a new
* peripheral.
* <p>
* Turtle upgrades are defined in two stages. First, one creates a {@link ITurtleUpgrade} subclass and corresponding
* {@link UpgradeType} instance, which are then registered in a registry.
* {@link UpgradeType} instance, which are then registered in a Minecraft registry.
* <p>
* You then write a JSON file in your mod's {@literal data/} folder. This is then parsed when the world is loaded, and
* the upgrade automatically registered. It is recommended this is done via
* <a href="../upgrades/UpgradeType.html#datagen">data generators</a>.
* the upgrade automatically registered.
*
* <h2>Example</h2>
* {@snippet lang="java" :
* // We use Forge's DeferredRegister to register our upgrade type. Fabric mods may register their type directly.
* static final DeferredRegister<UpgradeType<? extends ITurtleUpgrade>> TURTLE_UPGRADES = DeferredRegister.create(ITurtleUpgrade.typeRegistry(), "my_mod");
* <h3>Registering the upgrade type</h3>
* First, let's create a new class that implements {@link ITurtleUpgrade}. It is recommended to subclass
* {@link AbstractTurtleUpgrade}, as that provides a default implementation of most methods.
*
* // Register a new upgrade type called "my_upgrade".
* public static final RegistryObject<UpgradeType<MyUpgrade>> MY_UPGRADE =
* TURTLE_UPGRADES.register("my_upgrade", () -> UpgradeType.simple(MyUpgrade::new));
* {@snippet class=com.example.examplemod.ExampleTurtleUpgrade region=body}
*
* // Then in your constructor
* TURTLE_UPGRADES.register(bus);
* }
* Now we must construct a new upgrade type. In most cases, you can use one of the helper methods (e.g.
* {@link UpgradeType#simpleWithCustomItem(Function)}), rather than defining your own implementation.
*
* {@snippet class=com.example.examplemod.ExampleMod region=turtle_upgrades}
*
* We now must register this upgrade type. This is done the same way as you'd register blocks, items, or other
* Minecraft objects. The approach to do this will depend on mod-loader.
*
* <h4>Fabric</h4>
* {@snippet class=com.example.examplemod.FabricExampleMod region=turtle_upgrades}
*
* <h4>Forge</h4>
* {@snippet class=com.example.examplemod.ForgeExampleMod region=turtle_upgrades}
*
* <h3>Rendering the upgrade</h3>
* Next, we need to register a model for our upgrade. This is done by registering a
* {@link dan200.computercraft.api.client.turtle.TurtleUpgradeModeller} for your upgrade type.
*
* <h4>Fabric</h4>
* {@snippet class=com.example.examplemod.FabricExampleModClient region=turtle_modellers}
*
* <h4>Forge</h4>
* {@snippet class=com.example.examplemod.FabricExampleModClient region=turtle_modellers}
*
* <h3 id="datagen">Registering the upgrade itself</h3>
* Upgrades themselves are loaded from datapacks when a level is loaded. In order to register our new upgrade, we must
* create a new JSON file at {@code data/<my_mod>/computercraft/turtle_upgrade/<my_upgrade_id>.json}.
*
* {@snippet file=data/examplemod/computercraft/turtle_upgrade/example_turtle_upgrade.json}
*
* The {@code "type"} field points to the ID of the upgrade type we've just registered, while the other fields are read
* by the type itself. As our upgrade was defined with {@link UpgradeType#simpleWithCustomItem(Function)}, the
* {@code "item"} field will construct our upgrade with {@link Items#COMPASS}.
* <p>
* We can then define a new upgrade using JSON by placing the following in
* {@code data/<my_mod>/computercraft/turtle_upgrade/<my_upgrade_id>.json}.
* <p>
* {@snippet lang="json" :
* {
* "type": "my_mod:my_upgrade"
* }
* }
* <p>
* Finally, we need to register a model for our upgrade, see
* {@link dan200.computercraft.api.client.turtle.TurtleUpgradeModeller} for more information.
* Rather than manually creating the file, it is recommended to use data-generators to generate this file. First, we
* register our new upgrades into a {@linkplain PatchedRegistries patched registry}.
*
* {@snippet class=com.example.examplemod.data.TurtleUpgradeProvider region=body}
*
* Next, we must write these upgrades to disk. Vanilla does not have complete support for this yet, so this must be done
* with mod-loader-specific APIs.
*
* <h4>Fabric</h4>
* {@snippet class=com.example.examplemod.FabricExampleModDataGenerator region=turtle_upgrades}
*
* <h4>Forge</h4>
* {@snippet class=com.example.examplemod.ForgeExampleModDataGenerator region=turtle_upgrades}
*/
public interface ITurtleUpgrade extends UpgradeBase {
/**

View File

@ -25,19 +25,11 @@ import java.util.Optional;
/**
* A builder for custom turtle tool upgrades.
* <p>
* This can be used from your <a href="../upgrades/UpgradeType.html#datagen">data generator</a> code in order to
* This can be used from your <a href="./ITurtleUpgrade.html#datagen">data generator</a> code in order to
* register turtle tools for your mod's tools.
*
* <h2>Example:</h2>
* {@snippet lang = "java":
* import net.minecraft.data.worldgen.BootstrapContext;
* import net.minecraft.resources.ResourceLocation;
* import net.minecraft.world.item.Items;
*
* public void registerTool(BootstrapContext<ITurtleUpgrade> upgrades) {
* TurtleToolBuilder.tool(ResourceLocation.fromNamespaceAndPath("my_mod", "wooden_pickaxe"), Items.WOODEN_PICKAXE).register(upgrades);
* }
*}
* <h2>Example</h2>
* {@snippet class=com.example.examplemod.data.TurtleToolProvider region=body}
*/
public final class TurtleToolBuilder {
private final ResourceKey<ITurtleUpgrade> id;

View File

@ -7,9 +7,7 @@ package dan200.computercraft.api.upgrades;
import com.mojang.serialization.MapCodec;
import dan200.computercraft.api.pocket.IPocketUpgrade;
import dan200.computercraft.api.turtle.ITurtleUpgrade;
import dan200.computercraft.impl.upgrades.UpgradeTypeImpl;
import net.minecraft.core.registries.BuiltInRegistries;
import net.minecraft.data.registries.RegistryPatchGenerator;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.item.crafting.Recipe;
import net.minecraft.world.level.storage.loot.functions.LootItemFunction;
@ -23,13 +21,10 @@ import java.util.function.Function;
* follow a similar design to other dynamic content, such as {@linkplain Recipe recipes} or {@link LootItemFunction
* loot functions}.
* <p>
* First, one adds a new class implementing {@link ITurtleUpgrade} or {@link IPocketUpgrade}). This is responsible for
* handling all the logic of your upgrade.
* <p>
* However, the upgrades are not registered directly. Instead, each upgrade class should have a corresponding
* {@link UpgradeType}, which is responsible for loading the upgrade from a datapack. The upgrade type should then be
* registered in its appropriate registry ({@link ITurtleUpgrade#typeRegistry()},
* {@link IPocketUpgrade#typeRegistry()}).
* While the {@link ITurtleUpgrade}/{@link IPocketUpgrade} class should contain the core logic of the upgrade, they are
* not registered directly. Instead, each upgrade class has a corresponding {@link UpgradeType}, which is responsible
* for loading the upgrade from a datapack. The upgrade type should then be registered in its appropriate registry
* ({@link ITurtleUpgrade#typeRegistry()}, {@link IPocketUpgrade#typeRegistry()}).
* <p>
* In order to register the actual upgrade, a JSON file referencing your upgrade type should be added to a datapack. It
* is recommended to do this via the data generators.
@ -38,29 +33,7 @@ import java.util.function.Function;
* As turtle and pocket upgrades are just loaded using vanilla's dynamic loaders, one may use the same data generation
* tools as you would for any other dynamic registry.
* <p>
* This can typically be done by extending vanilla's built-in registries using {@link RegistryPatchGenerator}, and then
* writing out the new registries using {@code net.fabricmc.fabric.api.datagen.v1.provider.FabricDynamicRegistryProvider}
* on Fabric or {@code net.neoforged.neoforge.common.data.DatapackBuiltinEntriesProvider} on Forge.
* <p>
* {@snippet lang="java" :
* import dan200.computercraft.api.turtle.ITurtleUpgrade;
* import net.minecraft.Util;
* import net.minecraft.core.HolderLookup;
* import net.minecraft.core.RegistrySetBuilder;
* import net.minecraft.data.DataGenerator;
* import net.neoforged.neoforge.common.data.DatapackBuiltinEntriesProvider;
*
* import java.util.concurrent.CompletableFuture;
*
* public void generate(DataGenerator.PackGenerator output, CompletableFuture<HolderLookup.Provider> registries) {
* var newRegistries = RegistryPatchGenerator.createLookup(registries, Util.make(new RegistrySetBuilder(), builder -> {
* builder.add(ITurtleUpgrade.REGISTRY, upgrades -> {
* upgrades.register(ITurtleUpgrade.createKey(ResourceLocation.fromNamespaceAndPath("my_mod", "my_upgrade")), new MyUpgrade());
* });
* }));
* output.addProvider(o -> new DatapackBuiltinEntriesProvider(o, newRegistries, Set.of("my_mod")));
* }
* }
* See <a href="../turtle/ITurtleUpgrade.html#datagen">the turtle upgrade docs</a> for a concrete example.
*
* @param <T> The upgrade subclass that this upgrade type represents.
* @see ITurtleUpgrade

View File

@ -2,12 +2,9 @@
//
// SPDX-License-Identifier: MPL-2.0
package dan200.computercraft.impl.upgrades;
package dan200.computercraft.api.upgrades;
import com.mojang.serialization.MapCodec;
import dan200.computercraft.api.upgrades.UpgradeBase;
import dan200.computercraft.api.upgrades.UpgradeType;
import org.jetbrains.annotations.ApiStatus;
/**
* Simple implementation of {@link UpgradeType}.
@ -15,6 +12,5 @@ import org.jetbrains.annotations.ApiStatus;
* @param codec The codec to read/write upgrades with.
* @param <T> The upgrade subclass that this upgrade type represents.
*/
@ApiStatus.Internal
public record UpgradeTypeImpl<T extends UpgradeBase>(MapCodec<T> codec) implements UpgradeType<T> {
record UpgradeTypeImpl<T extends UpgradeBase>(MapCodec<T> codec) implements UpgradeType<T> {
}

View File

@ -0,0 +1,68 @@
<!--
SPDX-FileCopyrightText: 2025 The CC: Tweaked Developers
SPDX-License-Identifier: MPL-2.0
-->
<!DOCTYPE HTML>
<html lang="en">
<body>
<p>
This is the documentation for CC: Tweaked $modVersion for Minecraft $mcVersion. Documentation for other versions of
Minecraft are available on the CC: Tweaked website:
<ul>
<li><a href="/mc-1.20.x/javadoc/">Minecraft 1.20.1</a>
<li><a href="/mc-1.21.x/javadoc/">Minecraft 1.21.1</a>
</ul>
<h1>Quick links</h1>
<p>
You probably want to start in the following places:
<ul>
<li>{@linkplain dan200.computercraft.api.peripheral Registering new peripherals}</li>
<li>
{@link dan200.computercraft.api.lua.LuaFunction} and {@link dan200.computercraft.api.lua.IArguments} for
adding methods to your peripheral or Lua objects.
</li>
<li>{@linkplain dan200.computercraft.api.turtle.ITurtleUpgrade Turtle upgrades}</li>
<li>{@linkplain dan200.computercraft.api.pocket.IPocketUpgrade Pocket upgrades}</li>
</ul>
<h1>Using</h1>
<p>
CC: Tweaked is hosted on my maven repo, and so is relatively simple to depend on. You may wish to add a soft (or
hard) dependency in your <code>mods.toml</code> file, with the appropriate version bounds, to ensure that API
functionality you depend on is present.
<pre class="language language-groovy"><code>repositories {
maven {
url "https://maven.squiddev.cc"
content { includeGroup("cc.tweaked") }
}
}
dependencies {
// Vanilla (i.e. for multi-loader systems)
compileOnly("cc.tweaked:cc-tweaked-$mcVersion-common-api:$modVersion")
// Forge Gradle
compileOnly("cc.tweaked:cc-tweaked-$mcVersion-core-api:$modVersion")
compileOnly(fg.deobf("cc.tweaked:cc-tweaked-$mcVersion-forge-api:$modVersion"))
runtimeOnly(fg.deobf("cc.tweaked:cc-tweaked-$mcVersion-forge:$modVersion"))
// Fabric Loom
modCompileOnly("cc.tweaked:cc-tweaked-$mcVersion-fabric-api:$modVersion")
modRuntimeOnly("cc.tweaked:cc-tweaked-$mcVersion-fabric:$modVersion")
}
</code></pre>
<p>
You should also be careful to only use classes within the <code>dan200.computercraft.api</code> package. Non-API
classes are subject to change at any point. If you depend on functionality outside the API (or need to mixin to
CC:T), please <a href="https://github.com/cc-tweaked/CC-Tweaked/discussions/new/choose">start a discussion</a> to
let me know!
</body>
</html>

View File

@ -11,12 +11,6 @@ plugins {
id("cc-tweaked.publishing")
}
sourceSets {
main {
resources.srcDir("src/generated/resources")
}
}
minecraft {
accessWideners(
"src/main/resources/computercraft.accesswidener",
@ -115,20 +109,28 @@ val lintLua by tasks.registering(IlluaminateExec::class) {
doLast { if (System.getenv("GITHUB_ACTIONS") != null) println("::remove-matcher owner=illuaminate::") }
}
val runData by tasks.registering(MergeTrees::class) {
output = layout.projectDirectory.dir("src/generated/resources")
fun MergeTrees.configureForDatagen(source: SourceSet, outputFolder: String) {
output = layout.projectDirectory.dir(outputFolder)
for (loader in listOf("forge", "fabric")) {
mustRunAfter(":$loader:runData")
mustRunAfter(":$loader:$name")
source {
input {
from(project(":$loader").layout.buildDirectory.dir("generatedResources"))
from(project(":$loader").layout.buildDirectory.dir(source.getTaskName("generateResources", null)))
exclude(".cache")
}
output = project(":$loader").layout.projectDirectory.dir("src/generated/resources")
output = project(":$loader").layout.projectDirectory.dir(outputFolder)
}
}
}
val runData by tasks.registering(MergeTrees::class) {
configureForDatagen(sourceSets.main.get(), "src/generated/resources")
}
val runExampleData by tasks.registering(MergeTrees::class) {
configureForDatagen(sourceSets.examples.get(), "src/examples/generatedResources")
}
tasks.withType(GenerateModuleMetadata::class).configureEach { isEnabled = false }

View File

@ -0,0 +1,4 @@
{
"type": "examplemod:example_turtle_upgrade",
"item": "minecraft:compass"
}

View File

@ -0,0 +1,75 @@
package com.example.examplemod;
import dan200.computercraft.api.ComputerCraftAPI;
import dan200.computercraft.api.component.ComputerComponents;
import dan200.computercraft.api.lua.Coerced;
import dan200.computercraft.api.lua.ILuaAPI;
import dan200.computercraft.api.lua.LuaFunction;
import dan200.computercraft.api.turtle.ITurtleAccess;
import org.jetbrains.annotations.Nullable;
/**
* An example API that will be available on every turtle. This demonstrates both registering an API, and how to write
* Lua-facing functions.
* <p>
* This API is not available as a global (as {@link #getNames() returns nothing}), but is instead accessible via
* {@code require} (see {@link #getModuleName()}).
*
* <h2>Example</h2>
* <pre class="language language-lua">{@code
* local my_api = require("example.my_api")
* print("Turtle is facing " .. my_api.getDirection())
* }</pre>
*/
public class ExampleAPI implements ILuaAPI {
private final ITurtleAccess turtle;
public ExampleAPI(ITurtleAccess turtle) {
this.turtle = turtle;
}
public static void register() {
// @start region=register
ComputerCraftAPI.registerAPIFactory(computer -> {
// Read the turtle component.
var turtle = computer.getComponent(ComputerComponents.TURTLE);
// If present then add our API.
return turtle == null ? null : new ExampleAPI(turtle);
});
// @end region=register
}
@Override
public String[] getNames() {
return new String[0];
}
@Override
public @Nullable String getModuleName() {
return "example.my_api";
}
/**
* A Lua-facing function function that returns the direction the turtle is facing.
*
* @return The turtle's direction.
*/
@LuaFunction
public final String getDirection() {
return turtle.getDirection().getName();
}
/**
* A Lua-facing function using {@link Coerced}. Unlike a {@link LuaFunction} taking a raw {@link String}, this will
* accept any value, and convert it to a string.
*
* @param myString The value to write.
*/
// @start region=coerced
@LuaFunction
public final void writeString(Coerced<String> myString) {
String contents = myString.value();
System.out.println("Got " + contents);
}
// @end region=coerced
}

View File

@ -0,0 +1,39 @@
package com.example.examplemod;
import com.example.examplemod.data.TurtleUpgradeProvider;
import com.example.examplemod.peripheral.FurnacePeripheral;
import dan200.computercraft.api.ComputerCraftAPI;
import dan200.computercraft.api.upgrades.UpgradeType;
/**
* Our example mod, containing the various things we register.
* <p>
* This isn't an especially good template to follow! It's convenient for our example mod (as we need to be multi-loader
* compatible), but there's a good chance there's a better pattern to follow. For example, on Forge you'd use
* {@code DeferredRegister} to register things), and multi-loader mods probably have their own abstractions.
* <p>
* See {@code FabricExampleMod} and {@code ForgeExampleMod} for the actual mod entrypoints.
*/
public final class ExampleMod {
public static final String MOD_ID = "examplemod";
/**
* The upgrade type for our example turtle upgrade. See the documentation for {@link UpgradeType} or
* {@code FabricExampleMod}/{@code ForgeExampleMod} for how this is registered.
* <p>
* This only defines the upgrade type. See {@link TurtleUpgradeProvider} for defining the actual upgrade.
*/
// @start region=turtle_upgrades
public static final UpgradeType<ExampleTurtleUpgrade> EXAMPLE_TURTLE_UPGRADE = UpgradeType.simpleWithCustomItem(
ExampleTurtleUpgrade::new
);
// @end region=turtle_upgrades
public static void registerComputerCraft() {
// @start region=generic_source
ComputerCraftAPI.registerGenericSource(new FurnacePeripheral());
// @end region=generic_source
ExampleAPI.register();
}
}

View File

@ -0,0 +1,22 @@
package com.example.examplemod;
import dan200.computercraft.api.turtle.AbstractTurtleUpgrade;
import dan200.computercraft.api.turtle.TurtleUpgradeType;
import dan200.computercraft.api.upgrades.UpgradeType;
import net.minecraft.world.item.ItemStack;
/**
* An example turtle upgrade.
*/
// @start region=body
public class ExampleTurtleUpgrade extends AbstractTurtleUpgrade {
public ExampleTurtleUpgrade(ItemStack stack) {
super(TurtleUpgradeType.PERIPHERAL, "example", stack);
}
@Override
public UpgradeType<ExampleTurtleUpgrade> getType() {
return ExampleMod.EXAMPLE_TURTLE_UPGRADE;
}
}
// @end region=body

View File

@ -0,0 +1,23 @@
package com.example.examplemod.data;
import com.example.examplemod.ExampleMod;
import dan200.computercraft.api.turtle.ITurtleUpgrade;
import dan200.computercraft.api.turtle.TurtleToolBuilder;
import net.minecraft.data.worldgen.BootstrapContext;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.world.item.Items;
/**
* Extends the bootstrap registries with a new turtle tool.
* <p>
* See {@link TurtleUpgradeProvider} and {@link ITurtleUpgrade} for how this would be used in datagen.
*/
public class TurtleToolProvider {
// @start region=body
public static void addUpgrades(BootstrapContext<ITurtleUpgrade> upgrades) {
TurtleToolBuilder
.tool(ResourceLocation.fromNamespaceAndPath(ExampleMod.MOD_ID, "wooden_pickaxe"), Items.WOODEN_PICKAXE)
.register(upgrades);
}
// @end region=body
}

View File

@ -0,0 +1,37 @@
package com.example.examplemod.data;
import com.example.examplemod.ExampleMod;
import com.example.examplemod.ExampleTurtleUpgrade;
import dan200.computercraft.api.turtle.ITurtleUpgrade;
import net.minecraft.Util;
import net.minecraft.core.HolderLookup;
import net.minecraft.core.RegistrySetBuilder;
import net.minecraft.data.registries.RegistryPatchGenerator;
import net.minecraft.data.worldgen.BootstrapContext;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.item.Items;
import java.util.concurrent.CompletableFuture;
/**
* Extends the bootstrap registries with our {@linkplain ExampleTurtleUpgrade example turtle upgrade}.
*/
// @start region=body
public class TurtleUpgradeProvider {
// Register our turtle upgrades.
public static void addUpgrades(BootstrapContext<ITurtleUpgrade> upgrades) {
upgrades.register(
ITurtleUpgrade.createKey(ResourceLocation.fromNamespaceAndPath(ExampleMod.MOD_ID, "example_turtle_upgrade")),
new ExampleTurtleUpgrade(new ItemStack(Items.COMPASS))
);
}
// Set up the dynamic registries to contain our turtle upgrades.
public static CompletableFuture<RegistrySetBuilder.PatchedRegistries> makeUpgradeRegistry(CompletableFuture<HolderLookup.Provider> registries) {
return RegistryPatchGenerator.createLookup(registries, Util.make(new RegistrySetBuilder(), builder -> {
builder.add(ITurtleUpgrade.REGISTRY, TurtleUpgradeProvider::addUpgrades);
}));
}
}
// @end region=body

View File

@ -0,0 +1,12 @@
@ApiStatus.Internal
@DefaultQualifier(value = NonNull.class, locations = {
TypeUseLocation.RETURN,
TypeUseLocation.PARAMETER,
TypeUseLocation.FIELD,
})
package com.example.examplemod;
import org.checkerframework.checker.nullness.qual.NonNull;
import org.checkerframework.framework.qual.DefaultQualifier;
import org.checkerframework.framework.qual.TypeUseLocation;
import org.jetbrains.annotations.ApiStatus;

View File

@ -0,0 +1,39 @@
package com.example.examplemod.peripheral;
import dan200.computercraft.api.lua.LuaFunction;
import dan200.computercraft.api.peripheral.IPeripheral;
import net.minecraft.world.level.block.entity.BrewingStandBlockEntity;
import org.jetbrains.annotations.Nullable;
/**
* A peripheral that adds a {@code getFuel()} method to brewing stands. This demonstrates the usage of
* {@link IPeripheral}.
*
* @see dan200.computercraft.api.peripheral
* @see FurnacePeripheral Using {@code GenericPeripheral}.
*/
// @start region=body
public class BrewingStandPeripheral implements IPeripheral {
private final BrewingStandBlockEntity brewingStand;
public BrewingStandPeripheral(BrewingStandBlockEntity brewingStand) {
this.brewingStand = brewingStand;
}
@Override
public String getType() {
return "brewing_stand";
}
@LuaFunction
public final int getFuel() {
// Don't do it this way! Use an access widener/transformer to access the "fuel" field instead.
return brewingStand.saveWithoutMetadata(brewingStand.getLevel().registryAccess()).getInt("Fuel");
}
@Override
public boolean equals(@Nullable IPeripheral other) {
return other instanceof BrewingStandPeripheral o && brewingStand == o.brewingStand;
}
}
// @end region=body

View File

@ -0,0 +1,44 @@
package com.example.examplemod.peripheral;
import dan200.computercraft.api.lua.LuaFunction;
import dan200.computercraft.api.peripheral.AttachedComputerSet;
import dan200.computercraft.api.peripheral.IComputerAccess;
import dan200.computercraft.api.peripheral.IPeripheral;
import org.jetbrains.annotations.Nullable;
/**
* A peripheral that tracks what computers it is attached to.
*
* @see AttachedComputerSet
*/
// @start region=body
public class ComputerTrackingPeripheral implements IPeripheral {
private final AttachedComputerSet computers = new AttachedComputerSet();
@Override
public void attach(IComputerAccess computer) {
computers.add(computer);
}
@Override
public void detach(IComputerAccess computer) {
computers.remove(computer);
}
@LuaFunction
public final void sayHello() {
// Queue a "hello" event on each computer.
computers.forEach(x -> x.queueEvent("hello", x.getAttachmentName()));
}
@Override
public String getType() {
return "my_peripheral";
}
@Override
public boolean equals(@Nullable IPeripheral other) {
return this == other;
}
}
// @end region=body

View File

@ -0,0 +1,29 @@
package com.example.examplemod.peripheral;
import com.example.examplemod.ExampleMod;
import dan200.computercraft.api.lua.LuaFunction;
import dan200.computercraft.api.peripheral.GenericPeripheral;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.world.level.block.entity.AbstractFurnaceBlockEntity;
/**
* A peripheral that adds a {@code getBurnTime} method to furnaces. This is used to demonstrate the usage of
* {@link GenericPeripheral}.
*
* @see dan200.computercraft.api.peripheral
* @see BrewingStandPeripheral Using {@code IPeripheral}.
*/
// @start region=body
public class FurnacePeripheral implements GenericPeripheral {
@Override
public String id() {
return ResourceLocation.fromNamespaceAndPath(ExampleMod.MOD_ID, "furnace").toString();
}
@LuaFunction(mainThread = true)
public int getBurnTime(AbstractFurnaceBlockEntity furnace) {
// Don't do it this way! Use an access widener/transformer to access the "litTime" field instead.
return furnace.saveWithoutMetadata(furnace.getLevel().registryAccess()).getInt("BurnTime");
}
}
// @end region=body

View File

@ -747,6 +747,7 @@ public class TurtleAPI implements ILuaAPI {
* @throws LuaException If the slot is out of range.
* @cc.treturn nil|table Information about the given slot, or {@code nil} if it is empty.
* @cc.since 1.64
* @cc.changed 1.90.0 Added detailed parameter.
* @cc.usage Print the current slot, assuming it contains 13 dirt.
*
* <pre>{@code

View File

@ -8,10 +8,6 @@ plugins {
id("cc-tweaked")
}
java {
withJavadocJar()
}
// Due to the slightly circular nature of our API, add the main API jars to the javadoc classpath.
val docApi by configurations.registering {
isTransitive = false

View File

@ -10,13 +10,8 @@ package dan200.computercraft.api.lua;
* This is designed to be used with {@link LuaFunction} annotated functions, to mark an argument as being coerced to
* the given type, rather than requiring an exact type.
*
* <h2>Example:</h2>
* {@snippet lang="java" :
* @LuaFunction
* public final void doSomething(Coerced<String> myString) {
* var value = myString.value();
* }
* }
* <h2>Example</h2>
* {@snippet class=com.example.examplemod.ExampleAPI region=coerced}
*
* @param value The argument value.
* @param <T> The type of the underlying value.

View File

@ -18,22 +18,11 @@ import dan200.computercraft.api.peripheral.IPeripheral;
* by capabilities/the block lookup API take priority. Block entities which use this system are given a peripheral name
* determined by their id, rather than any peripheral provider, though additional types may be provided by overriding
* {@link GenericPeripheral#getType()}.
* <p>
* For example, the main CC: Tweaked mod defines a generic source for inventories, which works on {@code IItemHandler}s:
*
* {@snippet lang="java" :
* public class InventoryMethods implements GenericSource {
* @LuaFunction(mainThread = true)
* public int size(IItemHandler inventory) {
* return inventory.getSlots();
* }
*
* // ...
* }
* }
* <h2>Example</h2>
* {@snippet class=com.example.examplemod.peripheral.FurnacePeripheral region=body}
* <p>
* New capabilities or block lookups (those not built into Forge/Fabric) must be explicitly registered using the
* loader-specific API.
* New capabilities (those not built into Forge) must be explicitly registered using the loader-specific API.
*
* @see dan200.computercraft.api.ComputerCraftAPI#registerGenericSource(GenericSource)
*/

View File

@ -4,17 +4,6 @@
/**
* ComputerCraft's public API.
* <p>
* You probably want to start in the following places:
* <ul>
* <li>{@link dan200.computercraft.api.peripheral} for registering new peripherals.</li>
* <li>
* {@link dan200.computercraft.api.lua.LuaFunction} and {@link dan200.computercraft.api.lua.IArguments} for
* adding methods to your peripheral or Lua objects.
* </li>
* <li>{@link dan200.computercraft.api.turtle.ITurtleUpgrade} for turtle upgrades.</li>
* <li>{@link dan200.computercraft.api.pocket.IPocketUpgrade} for pocket upgrades.</li>
* </ul>
*/
@DefaultQualifier(value = NonNull.class, locations = {
TypeUseLocation.RETURN,

View File

@ -27,21 +27,7 @@ import java.util.function.Consumer;
*
* <h2>Example</h2>
*
* {@snippet lang="java" :
* public class MyPeripheral implements IPeripheral {
* private final AttachedComputerSet computers = new ComputerCollection();
*
* @Override
* public void attach(IComputerAccess computer) {
* computers.add(computer);
* }
*
* @Override
* public void detach(IComputerAccess computer) {
* computers.remove(computer);
* }
* }
* }
* {@snippet class=com.example.examplemod.peripheral.ComputerTrackingPeripheral region=body}
*
* @see IComputerAccess
* @see IPeripheral#attach(IComputerAccess)

View File

@ -47,36 +47,12 @@
* Then, we can start adding methods to your block entity. Each method should take its target type as the first
* argument, which in this case is a {@code AbstractFurnaceBlockEntity}. We then annotate this method with
* {@link dan200.computercraft.api.lua.LuaFunction} to expose it to computers.
* <p>
* {@snippet lang="java" :
* import dan200.computercraft.api.lua.LuaFunction;
* import dan200.computercraft.api.peripheral.GenericPeripheral;
* import net.minecraft.world.level.block.entity.AbstractFurnaceBlockEntity;
*
* public final class FurnacePeripheral implements GenericPeripheral {
* @Override
* public String id() {
* return "mymod:furnace";
* }
* {@snippet class=com.example.examplemod.peripheral.FurnacePeripheral region=body}
*
* @LuaFunction(mainThread = true)
* public int getBurnTime(AbstractFurnaceBlockEntity furnace) {
* return furnace.litTime;
* }
* }
* }
* <p>
* Finally, we need to register our peripheral, so that ComputerCraft is aware of it:
* <p>
* {@snippet lang="java" :
* import dan200.computercraft.api.ComputerCraftAPI;
*
* public class ComputerCraftCompat {
* public static void register() {
* ComputerCraftAPI.registerGenericSource(new FurnacePeripheral());
* }
* }
* }
* {@snippet class=com.example.examplemod.ExampleMod region=generic_source}
*
* <h3>Creating a {@code IPeripheral}</h3>
* First, we'll need to create a new class that implements {@link dan200.computercraft.api.peripheral.IPeripheral}. This
@ -84,72 +60,20 @@
* <p>
* We can then start adding peripheral methods to our class. Each method should be {@code final}, and annotated with
* {@link dan200.computercraft.api.lua.LuaFunction}.
* <p>
* {@snippet lang="java" :
* import dan200.computercraft.api.lua.LuaFunction;
* import dan200.computercraft.api.peripheral.IPeripheral;
* import net.minecraft.world.level.block.entity.AbstractFurnaceBlockEntity;
* import org.jetbrains.annotations.Nullable;
*
* public class FurnacePeripheral implements IPeripheral {
* private final AbstractFurnaceBlockEntity furnace;
* {@snippet class=com.example.examplemod.peripheral.BrewingStandPeripheral region=body}
*
* public FurnacePeripheral(AbstractFurnaceBlockEntity furnace) {
* this.furnace = furnace;
* }
*
* @Override
* public String getType() {
* return "furnace";
* }
*
* @LuaFunction(mainThread = true)
* public final int getBurnTime() {
* return furnace.litTime;
* }
*
* @Override
* public boolean equals(@Nullable IPeripheral other) {
* return this == other || other instanceof FurnacePeripheral p && furnace == p.furnace;
* }
* }
* }
* <p>
* Finally, we'll need to register our peripheral. This is done with capabilities on Forge, or the block lookup API on
* Fabric.
*
* <h4>Registering {@code IPeripheral} on Forge</h4>
* Registering a peripheral on Forge can be done by using the capability API, via {@code PeripheralCapability}.
*
* {@snippet lang="java" :
* import dan200.computercraft.api.peripheral.PeripheralCapability;
* import net.minecraft.world.level.block.entity.BlockEntityType;
* import net.neoforged.neoforge.capabilities.RegisterCapabilitiesEvent;
*
* public class ComputerCraftCompat {
* public static void register(RegisterCapabilitiesEvent event) {
* event.registerBlockEntity(PeripheralCapability.get(), BlockEntityType.FURNACE, (f, s) -> new FurnacePeripheral(f));
* event.registerBlockEntity(PeripheralCapability.get(), BlockEntityType.BLAST_FURNACE, (f, s) -> new FurnacePeripheral(f));
* event.registerBlockEntity(PeripheralCapability.get(), BlockEntityType.SMOKER, (f, s) -> new FurnacePeripheral(f));
* }
* }
* }
* {@snippet class=com.example.examplemod.ForgeExampleMod region=peripherals}
*
* <h4>Registering {@code IPeripheral} on Fabric</h4>
* Registering a peripheral on Fabric can be done using the block lookup API, via {@code PeripheralLookup}.
*
* {@snippet lang="java" :
* import dan200.computercraft.api.peripheral.PeripheralLookup;
* import dan200.computercraft.example.FurnacePeripheral;
* import net.minecraft.world.level.block.entity.BlockEntityType;
*
* public class ComputerCraftCompat {
* public static void register() {
* PeripheralLookup.get().registerForBlockEntity((f, s) -> new FurnacePeripheral(f), BlockEntityType.FURNACE);
* PeripheralLookup.get().registerForBlockEntity((f, s) -> new FurnacePeripheral(f), BlockEntityType.BLAST_FURNACE);
* PeripheralLookup.get().registerForBlockEntity((f, s) -> new FurnacePeripheral(f), BlockEntityType.SMOKER);
* }
* }
* }
* {@snippet class=com.example.examplemod.FabricExampleMod region=peripherals}
*/
package dan200.computercraft.api.peripheral;

View File

@ -13,7 +13,7 @@ import java.util.Set;
* This is intended for logging errors where the message content is supplied from untrusted sources. This isn't a
* perfect escaping mechanism, but ensures basic "unsafe" strings (i.e. ANSI escape sequences, long lines) are escaped.
*
* <h2>Example:</h2>
* <h2>Example</h2>
* <pre>{@code
* LOG.error("Some error occurred: {}", new TruncatedError(error));
* }</pre>

View File

@ -223,16 +223,21 @@ end
--- Returns true if a path is mounted to the parent filesystem.
--
-- The root filesystem "/" is considered a mount, along with disk folders and
-- the rom folder. Other programs (such as network shares) can exstend this to
-- make other mount types by correctly assigning their return value for getDrive.
-- the rom folder.
--
-- @tparam string path The path to check.
-- @treturn boolean If the path is mounted, rather than a normal file/folder.
-- @throws If the path does not exist.
-- @see getDrive
-- @since 1.87.0
function fs.isDriveRoot(sPath)
expect(1, sPath, "string")
function fs.isDriveRoot(path)
expect(1, path, "string")
local parent = fs.getDir(path)
-- Force the root directory to be a mount.
return fs.getDir(sPath) == ".." or fs.getDrive(sPath) ~= fs.getDrive(fs.getDir(sPath))
if parent == ".." then return true end
local drive = fs.getDrive(path)
return drive ~= nil and drive ~= fs.getDrive(parent)
end

View File

@ -147,7 +147,7 @@ handleMetatable = {
if format == "l" then
if handle.readLine then res = handle.readLine() end
elseif format == "L" and handle.readLine then
elseif format == "L" then
if handle.readLine then res = handle.readLine(true) end
elseif format == "a" then
if handle.readAll then res = handle.readAll() or "" end

View File

@ -35,7 +35,7 @@ local function getFilename(sUrl)
return sUrl:match("/([^/]+)$")
end
local function get(sUrl)
local function get(url)
-- Check if the URL is valid
local ok, err = http.checkURL(url)
if not ok then
@ -43,12 +43,12 @@ local function get(sUrl)
return
end
write("Connecting to " .. sUrl .. "... ")
write("Connecting to " .. url .. "... ")
local response = http.get(sUrl)
local response, err = http.get(url)
if not response then
print("Failed.")
return nil
printError(err)
return
end
print("Success.")

View File

@ -104,7 +104,7 @@ while running do
end
else
printError(results[2])
require "cc.internal.exception".report(results[2], results[3], chunk_map)
exception.report(results[2], results[3], chunk_map)
end
else
local parser = require "cc.internal.syntax"

View File

@ -83,6 +83,10 @@ describe("The fs library", function()
expect(fs.isDriveRoot("/rom/startup.lua")):eq(false)
expect(fs.isDriveRoot("/rom/programs/delete.lua")):eq(false)
end)
it("returns false for missing files", function()
expect(fs.isDriveRoot("does_not_exist")):eq(false)
end)
end)
describe("fs.list", function()
@ -555,6 +559,22 @@ describe("The fs library", function()
end)
end)
describe("fs.getDrive", function()
it("returns the drive for the mount roots", function()
expect(fs.getDrive("")):eq("hdd")
expect(fs.getDrive("rom")):eq("rom")
end)
it("returns the drive for subdirectories", function()
expect(fs.getDrive("rom/startup.lua")):eq("rom")
end)
it("returns nothing for missing files", function()
-- Peculiar, but we return no values, rather than nil!
expect(table.pack(fs.getDrive("no_such_file"))):same { n = 0 }
end)
end)
describe("fs.attributes", function()
it("errors on non-existent files", function()
expect.error(fs.attributes, "xuxu_nao_existe"):eq("/xuxu_nao_existe: No such file")

View File

@ -7,10 +7,6 @@ plugins {
id("cc-tweaked.publishing")
}
java {
withJavadocJar()
}
cct.inlineProject(":common-api")
dependencies {

View File

@ -106,8 +106,6 @@ dependencies {
testFixturesImplementation(testFixtures(project(":core")))
}
sourceSets.main { resources.srcDir("src/generated/resources") }
loom {
accessWidenerPath.set(project(":common").file("src/main/resources/computercraft.accesswidener"))
mixin.useLegacyMixinAp = false
@ -126,6 +124,10 @@ loom {
sourceSet(sourceSets.testMod.get())
sourceSet(project(":common").sourceSets.testMod.get())
}
register("examplemod") {
sourceSet(sourceSets.examples.get())
}
}
runs {
@ -143,19 +145,24 @@ loom {
runDir("run/server")
}
register("data") {
configName = "Datagen"
fun RunConfigSettings.configureForData(sourceSet: SourceSet) {
client()
source(sourceSets.datagen.get())
runDir("run/dataGen")
runDir("run/run${name.capitalise()}")
property("fabric-api.datagen")
property("fabric-api.datagen.output-dir", layout.buildDirectory.dir("generatedResources").getAbsolutePath())
property(
"fabric-api.datagen.output-dir",
layout.buildDirectory.dir(sourceSet.getTaskName("generateResources", null)).getAbsolutePath(),
)
property("fabric-api.datagen.strict-validation")
}
fun configureForGameTest(config: RunConfigSettings) = config.run {
register("data") {
configName = "Datagen"
configureForData(sourceSets.main.get())
source(sourceSets.datagen.get())
}
fun RunConfigSettings.configureForGameTest() {
source(sourceSets.testMod.get())
val testSources = project(":common").file("src/testMod/resources/data/cctest").absolutePath
@ -170,7 +177,7 @@ loom {
val testClient by registering {
configName = "Test Client"
client()
configureForGameTest(this)
configureForGameTest()
runDir("run/testClient")
property("cctest.tags", "client,common")
@ -179,16 +186,27 @@ loom {
register("gametest") {
configName = "Game Test"
server()
configureForGameTest(this)
configureForGameTest()
property("fabric-api.gametest")
property(
"fabric-api.gametest.report-file",
layout.buildDirectory.dir("test-results/runGametest.xml")
.getAbsolutePath(),
layout.buildDirectory.dir("test-results/runGametest.xml").getAbsolutePath(),
)
runDir("run/gametest")
}
register("exampleClient") {
client()
configName = "Example Mod Client"
source(sourceSets.examples.get())
}
register("exampleData") {
configName = "Example Mod Datagen"
configureForData(sourceSets.examples.get())
source(sourceSets.examples.get())
}
}
}

View File

@ -0,0 +1,31 @@
package com.example.examplemod;
import com.example.examplemod.peripheral.BrewingStandPeripheral;
import dan200.computercraft.api.peripheral.PeripheralLookup;
import dan200.computercraft.api.turtle.ITurtleUpgrade;
import dan200.computercraft.api.upgrades.UpgradeType;
import net.fabricmc.api.ModInitializer;
import net.minecraft.core.Registry;
import net.minecraft.core.registries.BuiltInRegistries;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.world.level.block.entity.BlockEntityType;
/**
* The main entry point for our example mod.
*/
public class FabricExampleMod implements ModInitializer {
@Override
public void onInitialize() {
// @start region=turtle_upgrades
@SuppressWarnings("unchecked")
var turtleUpgradeSerialisers = (Registry<UpgradeType<? extends ITurtleUpgrade>>) BuiltInRegistries.REGISTRY.get(ITurtleUpgrade.typeRegistry().location());
Registry.register(turtleUpgradeSerialisers, ResourceLocation.fromNamespaceAndPath(ExampleMod.MOD_ID, "example_turtle_upgrade"), ExampleMod.EXAMPLE_TURTLE_UPGRADE);
// @end region=turtle_upgrades
ExampleMod.registerComputerCraft();
// @start region=peripherals
PeripheralLookup.get().registerForBlockEntity((f, s) -> new BrewingStandPeripheral(f), BlockEntityType.BREWING_STAND);
// @end region=peripherals
}
}

View File

@ -0,0 +1,14 @@
package com.example.examplemod;
import dan200.computercraft.api.client.FabricComputerCraftAPIClient;
import dan200.computercraft.api.client.turtle.TurtleUpgradeModeller;
import net.fabricmc.api.ClientModInitializer;
public class FabricExampleModClient implements ClientModInitializer {
@Override
public void onInitializeClient() {
// @start region=turtle_modellers
FabricComputerCraftAPIClient.registerTurtleUpgradeModeller(ExampleMod.EXAMPLE_TURTLE_UPGRADE, TurtleUpgradeModeller.flatItem());
// @end region=turtle_modellers
}
}

View File

@ -0,0 +1,49 @@
package com.example.examplemod;
import com.example.examplemod.data.TurtleUpgradeProvider;
import net.fabricmc.fabric.api.datagen.v1.DataGeneratorEntrypoint;
import net.fabricmc.fabric.api.datagen.v1.FabricDataGenerator;
import net.fabricmc.fabric.api.datagen.v1.FabricDataOutput;
import net.fabricmc.fabric.api.datagen.v1.provider.FabricDynamicRegistryProvider;
import net.fabricmc.fabric.api.event.registry.DynamicRegistries;
import net.minecraft.core.HolderLookup;
import net.minecraft.core.RegistrySetBuilder;
import java.util.concurrent.CompletableFuture;
/**
* Data generators for our Fabric example mod.
*/
public class FabricExampleModDataGenerator implements DataGeneratorEntrypoint {
@Override
public void onInitializeDataGenerator(FabricDataGenerator generator) {
var pack = generator.createPack();
addTurtleUpgrades(pack, generator.getRegistries());
}
// @start region=turtle_upgrades
private static void addTurtleUpgrades(FabricDataGenerator.Pack pack, CompletableFuture<HolderLookup.Provider> registries) {
var fullRegistryPatch = TurtleUpgradeProvider.makeUpgradeRegistry(registries);
pack.addProvider((FabricDataOutput output) -> new AutomaticDynamicRegistryProvider(output, fullRegistryPatch));
}
/**
* A subclass of {@link FabricDynamicRegistryProvider} that writes all new entries.
*/
private static class AutomaticDynamicRegistryProvider extends FabricDynamicRegistryProvider {
AutomaticDynamicRegistryProvider(FabricDataOutput output, CompletableFuture<RegistrySetBuilder.PatchedRegistries> registries) {
super(output, registries.thenApply(RegistrySetBuilder.PatchedRegistries::patches));
}
@Override
protected void configure(HolderLookup.Provider registries, Entries entries) {
for (var r : DynamicRegistries.getDynamicRegistries()) entries.addAll(registries.lookupOrThrow(r.key()));
}
@Override
public String getName() {
return "Registries";
}
}
// @end region=turtle_upgrades
}

View File

@ -0,0 +1,16 @@
{
"schemaVersion": 1,
"id": "examplemod",
"version": "1.0.0",
"entrypoints": {
"main": [
"com.example.examplemod.FabricExampleMod"
],
"fabric-datagen": [
"com.example.examplemod.FabricExampleModDataGenerator"
]
},
"depends": {
"computercraft": "*"
}
}

View File

@ -7,10 +7,6 @@ plugins {
id("cc-tweaked.publishing")
}
java {
withJavadocJar()
}
cct.inlineProject(":common-api")
dependencies {

View File

@ -3,7 +3,7 @@
// SPDX-License-Identifier: MPL-2.0
import cc.tweaked.gradle.*
import net.neoforged.gradle.dsl.common.runs.run.Run
import net.neoforged.moddevgradle.dsl.RunModel
plugins {
id("cc-tweaked.forge")
@ -19,106 +19,122 @@ cct {
allProjects.forEach { externalSources(it) }
}
sourceSets {
main {
resources.srcDir("src/generated/resources")
}
testMod { runs { modIdentifier = "cctest" } }
testFixtures { runs { modIdentifier = "cctest" } }
}
minecraft {
accessTransformers {
file("src/main/resources/META-INF/accesstransformer.cfg")
}
}
runs {
configureEach {
systemProperty("forge.logging.markers", "REGISTRIES")
systemProperty("forge.logging.console.level", "debug")
neoForge {
val computercraft by mods.registering {
cct.sourceDirectories.get().forEach {
if (it.classes) modSources.add("computercraft", it.sourceSet)
}
dependencies {
runtime(configurations["minecraftLibrary"])
if (it.classes) sourceSet(it.sourceSet)
}
}
val client by registering {
workingDirectory(file("run"))
}
val server by registering {
workingDirectory(file("run/server"))
argument("--nogui")
}
val data by registering {
workingDirectory(file("run"))
arguments.addAll(
"--mod", "computercraft", "--all",
"--output", layout.buildDirectory.dir("generatedResources").getAbsolutePath(),
"--existing", project(":common").file("src/main/resources/").absolutePath,
"--existing", file("src/main/resources/").absolutePath,
)
modSources.add("computercraft", sourceSets.datagen.get())
}
fun Run.configureForGameTest() {
gameTest()
systemProperty("cctest.sources", project(":common").file("src/testMod/resources/data/cctest").absolutePath)
modSource(sourceSets.testMod.get())
modSource(sourceSets.testFixtures.get())
modSources.add("cctest", project(":core").sourceSets.testFixtures.get())
jvmArgument("-ea")
dependencies {
runtime(configurations["testMinecraftLibrary"])
val computercraftDatagen by mods.registering {
cct.sourceDirectories.get().forEach {
if (it.classes) sourceSet(it.sourceSet)
}
sourceSet(sourceSets.datagen.get())
}
val gameTestServer by registering {
workingDirectory(file("run/testServer"))
configureForGameTest()
val testMod by mods.registering {
sourceSet(sourceSets.testMod.get())
sourceSet(sourceSets.testFixtures.get())
sourceSet(project(":core").sourceSets["testFixtures"])
}
val gameTestClient by registering {
configure(runTypes.named("client"))
val exampleMod by mods.registering {
sourceSet(sourceSets.examples.get())
}
workingDirectory(file("run/testClient"))
configureForGameTest()
runs {
configureEach {
ideName = "Forge - ${name.capitalise()}"
systemProperty("forge.logging.markers", "REGISTRIES")
systemProperty("forge.logging.console.level", "debug")
loadedMods.add(computercraft)
}
systemProperties("cctest.tags", "client,common")
register("client") {
client()
}
register("server") {
server()
gameDirectory = file("run/server")
programArgument("--nogui")
}
fun RunModel.configureForData(mod: String, sourceSet: SourceSet) {
data()
gameDirectory = file("run/run${name.capitalise()}")
programArguments.addAll(
"--mod", mod, "--all",
"--output",
layout.buildDirectory.dir(sourceSet.getTaskName("generateResources", null))
.getAbsolutePath(),
"--existing", project.project(":common").file("src/${sourceSet.name}/resources/").absolutePath,
"--existing", project.file("src/${sourceSet.name}/resources/").absolutePath,
)
}
register("data") {
configureForData("computercraft", sourceSets.main.get())
loadedMods = listOf(computercraftDatagen.get())
}
fun RunModel.configureForGameTest() {
systemProperty(
"cctest.sources",
project.project(":common").file("src/testMod/resources/data/cctest").absolutePath,
)
programArgument("--mixin.config=computercraft-gametest.mixins.json")
loadedMods.add(testMod)
jvmArgument("-ea")
}
register("testClient") {
client()
gameDirectory = file("run/testClient")
configureForGameTest()
systemProperty("cctest.tags", "client,common")
}
register("gametest") {
type = "gameTestServer"
configureForGameTest()
systemProperty("forge.logging.console.level", "info")
systemProperty(
"cctest.gametest-report",
layout.buildDirectory.dir("test-results/runGametest.xml").getAbsolutePath(),
)
gameDirectory = file("run/gametest")
}
register("exampleClient") {
client()
loadedMods.add(exampleMod.get())
}
register("exampleData") {
configureForData("examplemod", sourceSets.examples.get())
loadedMods.add(exampleMod.get())
}
}
}
configurations {
val minecraftEmbed by registering {
isCanBeResolved = false
isCanBeConsumed = false
}
named("jarJar") { extendsFrom(minecraftEmbed.get()) }
additionalRuntimeClasspath { extendsFrom(jarJar.get()) }
val minecraftLibrary by registering {
isCanBeResolved = true
isCanBeConsumed = false
extendsFrom(minecraftEmbed.get())
}
runtimeOnly { extendsFrom(minecraftLibrary.get()) }
val testMinecraftLibrary by registering {
val testAdditionalRuntimeClasspath by registering {
isCanBeResolved = true
isCanBeConsumed = false
// Prevent ending up with multiple versions of libraries on the classpath.
shouldResolveConsistentlyWith(minecraftLibrary.get())
shouldResolveConsistentlyWith(additionalRuntimeClasspath.get())
}
for (testConfig in listOf("testClientAdditionalRuntimeClasspath", "gametestAdditionalRuntimeClasspath")) {
named(testConfig) { extendsFrom(testAdditionalRuntimeClasspath.get()) }
}
register("testWithIris") {
@ -136,21 +152,18 @@ dependencies {
runtimeOnly(libs.bundles.externalMods.forge.runtime) { cct.exclude(this) }
compileOnly(variantOf(libs.create.forge) { classifier("slim") }) { isTransitive = false }
implementation("net.neoforged:neoforge:${libs.versions.neoForge.get()}")
// Depend on our other projects.
api(commonClasses(project(":forge-api"))) { cct.exclude(this) }
clientApi(clientClasses(project(":forge-api"))) { cct.exclude(this) }
implementation(project(":core")) { cct.exclude(this) }
"minecraftEmbed"(libs.cobalt)
"minecraftEmbed"(libs.jzlib)
jarJar(libs.cobalt)
jarJar(libs.jzlib)
// We don't jar-in-jar our additional netty dependencies (see the tasks.jarJar configuration), but still want them
// on the legacy classpath.
"minecraftLibrary"(libs.netty.http)
"minecraftLibrary"(libs.netty.socks)
"minecraftLibrary"(libs.netty.proxy)
additionalRuntimeClasspath(libs.netty.http) { isTransitive = false }
additionalRuntimeClasspath(libs.netty.socks) { isTransitive = false }
additionalRuntimeClasspath(libs.netty.proxy) { isTransitive = false }
testFixturesApi(libs.bundles.test)
testFixturesApi(libs.bundles.kotlin)
@ -163,8 +176,8 @@ dependencies {
testModImplementation(testFixtures(project(":forge")))
// Ensure our test fixture dependencies are on the classpath
"testMinecraftLibrary"(libs.bundles.kotlin)
"testMinecraftLibrary"(libs.bundles.test)
"testAdditionalRuntimeClasspath"(libs.bundles.kotlin)
"testAdditionalRuntimeClasspath"(libs.bundles.test)
testFixturesImplementation(testFixtures(project(":core")))
@ -178,15 +191,12 @@ tasks.processResources {
inputs.property("modVersion", modVersion)
inputs.property("neoVersion", libs.versions.neoForge.get())
filesMatching("META-INF/neoforge.mods.toml") {
filesMatching("META-INF/mods.toml") {
expand(mapOf("neoVersion" to libs.versions.neoForge.get(), "file" to mapOf("jarVersion" to modVersion)))
}
}
tasks.jar {
archiveClassifier.set("slim")
duplicatesStrategy = DuplicatesStrategy.FAIL
// Include all classes from other projects except core.
val coreSources = project(":core").sourceSets["main"]
for (source in cct.sourceDirectories.get()) {
@ -203,42 +213,28 @@ tasks.sourcesJar {
for (source in cct.sourceDirectories.get()) from(source.sourceSet.allSource)
}
tasks.jarJar {
archiveClassifier.set("")
}
tasks.assemble { dependsOn("jarJar") }
// Check tasks
tasks.test {
systemProperty("cct.test-files", layout.buildDirectory.dir("tmp/testFiles").getAbsolutePath())
}
val runGametest by tasks.registering(JavaExec::class) {
group = LifecycleBasePlugin.VERIFICATION_GROUP
description = "Runs tests on a temporary Minecraft instance."
dependsOn("cleanRunGametest")
val runGametest = tasks.named<JavaExec>("runGametest") {
usesService(MinecraftRunnerService.get(gradle))
copyFromTask("runGameTestServer")
systemProperty("forge.logging.console.level", "info")
systemProperty("cctest.gametest-report", layout.buildDirectory.dir("test-results/$name.xml").getAbsolutePath())
}
cct.jacoco(runGametest)
tasks.check { dependsOn(runGametest) }
val runGametestClient by tasks.registering(ClientJavaExec::class) {
description = "Runs client-side gametests with no mods"
copyFrom("runGameTestClient")
copyFromForge("runTestClient")
tags("client")
}
cct.jacoco(runGametestClient)
val runGametestClientWithIris by tasks.registering(ClientJavaExec::class) {
description = "Runs client-side gametests with Iris"
copyFrom("runGameTestClient")
copyFromForge("runGameTestClient")
tags("iris")
classpath += configurations["testWithIris"]
@ -256,20 +252,15 @@ tasks.register("checkClient") {
// Upload tasks
modPublishing {
output.set(tasks.jarJar)
output.set(tasks.jar)
}
// Don't publish the slim jar
for (cfg in listOf(configurations.apiElements, configurations.runtimeElements)) {
cfg.configure { artifacts.removeIf { it.classifier == "slim" } }
}
tasks.withType(GenerateModuleMetadata::class).configureEach { isEnabled = false }
publishing {
publications {
named("maven", MavenPublication::class) {
mavenDependencies {
cct.configureExcludes(this)
exclude(libs.jei.forge.get())
}
}
}

View File

@ -0,0 +1,41 @@
package com.example.examplemod;
import com.example.examplemod.peripheral.BrewingStandPeripheral;
import dan200.computercraft.api.peripheral.PeripheralCapability;
import dan200.computercraft.api.turtle.ITurtleUpgrade;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.world.level.block.entity.BlockEntityType;
import net.neoforged.bus.api.IEventBus;
import net.neoforged.fml.common.Mod;
import net.neoforged.fml.event.lifecycle.FMLCommonSetupEvent;
import net.neoforged.neoforge.capabilities.RegisterCapabilitiesEvent;
import net.neoforged.neoforge.registries.RegisterEvent;
/**
* The main entry point for the Forge version of our example mod.
*/
@Mod(ExampleMod.MOD_ID)
public class ForgeExampleMod {
public ForgeExampleMod(IEventBus modBus) {
// Register our turtle upgrade. If writing a Forge-only mod, you'd normally use DeferredRegister instead.
// However, this is an easy way to implement this in a multi-loader-compatible manner.
// @start region=turtle_upgrades
modBus.addListener((RegisterEvent event) -> {
event.register(
ITurtleUpgrade.typeRegistry(),
ResourceLocation.fromNamespaceAndPath(ExampleMod.MOD_ID, "example_turtle_upgrade"),
() -> ExampleMod.EXAMPLE_TURTLE_UPGRADE
);
});
// @end region=turtle_upgrades
modBus.addListener((FMLCommonSetupEvent event) -> ExampleMod.registerComputerCraft());
// @start region=peripherals
modBus.addListener((RegisterCapabilitiesEvent event) -> {
event.registerBlockEntity(PeripheralCapability.get(), BlockEntityType.BREWING_STAND, (b, d) -> new BrewingStandPeripheral(b));
});
// @end region=peripherals
}
}

View File

@ -0,0 +1,20 @@
package com.example.examplemod;
import dan200.computercraft.api.client.turtle.RegisterTurtleModellersEvent;
import dan200.computercraft.api.client.turtle.TurtleUpgradeModeller;
import net.neoforged.api.distmarker.Dist;
import net.neoforged.bus.api.SubscribeEvent;
import net.neoforged.fml.common.EventBusSubscriber;
/**
* The client-side entry point for the Forge version of our example mod.
*/
@EventBusSubscriber(modid = ExampleMod.MOD_ID, value = Dist.CLIENT, bus = EventBusSubscriber.Bus.MOD)
public class ForgeExampleModClient {
// @start region=turtle_modellers
@SubscribeEvent
public static void onRegisterTurtleModellers(RegisterTurtleModellersEvent event) {
event.register(ExampleMod.EXAMPLE_TURTLE_UPGRADE, TurtleUpgradeModeller.flatItem());
}
// @end region=turtle_modellers
}

View File

@ -0,0 +1,31 @@
package com.example.examplemod;
import com.example.examplemod.data.TurtleUpgradeProvider;
import net.minecraft.core.HolderLookup;
import net.minecraft.data.DataGenerator;
import net.neoforged.bus.api.SubscribeEvent;
import net.neoforged.fml.common.EventBusSubscriber;
import net.neoforged.neoforge.common.data.DatapackBuiltinEntriesProvider;
import net.neoforged.neoforge.data.event.GatherDataEvent;
import java.util.Set;
import java.util.concurrent.CompletableFuture;
/**
* Data generators for the Forge version of our example mod.
*/
@EventBusSubscriber(bus = EventBusSubscriber.Bus.MOD)
public class ForgeExampleModDataGenerator {
@SubscribeEvent
public static void gather(GatherDataEvent event) {
var pack = event.getGenerator().getVanillaPack(true);
addTurtleUpgrades(pack, event.getLookupProvider());
}
// @start region=turtle_upgrades
private static void addTurtleUpgrades(DataGenerator.PackGenerator pack, CompletableFuture<HolderLookup.Provider> registries) {
var fullRegistryPatch = TurtleUpgradeProvider.makeUpgradeRegistry(registries);
pack.addProvider(o -> new DatapackBuiltinEntriesProvider(o, fullRegistryPatch, Set.of(ExampleMod.MOD_ID)));
}
// @end region=turtle_upgrades
}

View File

@ -0,0 +1,14 @@
modLoader="javafml"
loaderVersion="[1,)"
license="CC0-1.0"
[[mods]]
modId="examplemod"
version="1.0.0"
[[dependencies.examplemod]]
modId="computercraft"
mandatory=true
versionRange="[1.0,)"
ordering="AFTER"
side="BOTH"

View File

@ -0,0 +1,6 @@
{
"pack": {
"pack_format": 15,
"description": "Example Mod"
}
}

View File

@ -474,4 +474,4 @@
"count": 1
}
}
}
}

View File

@ -8,14 +8,10 @@ pluginManagement {
mavenCentral()
gradlePluginPortal()
maven("https://maven.neoforged.net/releases") {
maven("https://maven.neoforged.net") {
name = "NeoForge"
content {
includeGroup("net.minecraftforge")
includeGroup("net.neoforged")
includeGroup("net.neoforged.gradle")
includeModule("codechicken", "DiffPatch")
includeModule("net.covers1624", "Quack")
}
}