mirror of
https://github.com/SquidDev-CC/CC-Tweaked
synced 2025-02-02 20:29:13 +00:00
Clean up Javadocs a little
I've no motivation for modding right now, but always got time for build
system busywork!
CC:T (and CC before that) has always published its API docs. However,
they're not always the most helpful — they're useful if you know what
you're looking for, but aren't a good getting-started guide.
Part of the issue here is there's no examples, and everything is
described pretty abstractly. I have occasionally tried to improve this
(e.g. the peripheral docs in bdffabc08e
),
but it's a long road.
This commit adds a new example mod, which registers peripherals, an API
and a turtle upgrade. While the mod itself isn't exported as part of the
docs, we reference blocks of it using Java's new {@snippet} tag.
- Switch the Forge project to use NeoForge's new Legacy MDG plugin. We
don't *need* to do this, but it means the build logic for Forge and
NeoForge is more closely aligned.
- Add a new SnippetTaglet, which is a partial backport of Java 18+'s
{@snippet}.
- Add an example mod. This is a working multi-loader mod, complete with
datagen (albeit with no good multi-loader abstractions).
- Move our existing <pre>{@code ...}</pre> blocks into the example mod,
replacing them with {@snippet}s.
- Add a new overview page to the docs, providing some getting-started
information. We had this already in the dan200.computercraft.api
package docs, but it's not especially visible there.
This commit is contained in:
parent
d9fc1c3a80
commit
3c46b8acd7
@ -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
|
||||
)
|
||||
|
13
README.md
13
README.md
@ -61,19 +61,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!
|
||||
|
19
REUSE.toml
19
REUSE.toml
@ -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]]
|
||||
@ -46,7 +51,6 @@ path = [
|
||||
"projects/fabric/src/main/resources/fabric.mod.json",
|
||||
"projects/fabric/src/testMod/resources/computercraft-gametest.fabric.mixins.json",
|
||||
"projects/fabric/src/testMod/resources/fabric.mod.json",
|
||||
"projects/forge/src/client/resources/computercraft-client.forge.mixins.json",
|
||||
"projects/web/src/frontend/mount/.settings",
|
||||
"projects/web/src/frontend/mount/example.nfp",
|
||||
"projects/web/src/frontend/mount/example.nft",
|
||||
@ -73,7 +77,7 @@ path = [
|
||||
]
|
||||
|
||||
[[annotations]]
|
||||
# Community-contributed license files
|
||||
# Community-contributed language files
|
||||
SPDX-FileCopyrightText = "2017 The CC: Tweaked Developers"
|
||||
SPDX-License-Identifier = "LicenseRef-CCPL"
|
||||
path = [
|
||||
@ -87,18 +91,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"
|
||||
|
@ -14,18 +14,10 @@ repositories {
|
||||
mavenCentral()
|
||||
gradlePluginPortal()
|
||||
|
||||
maven("https://maven.minecraftforge.net") {
|
||||
name = "Forge"
|
||||
maven("https://maven.neoforged.net") {
|
||||
name = "NeoForge"
|
||||
content {
|
||||
includeGroup("net.minecraftforge")
|
||||
includeGroup("net.minecraftforge.gradle")
|
||||
}
|
||||
}
|
||||
|
||||
maven("https://maven.parchmentmc.org") {
|
||||
name = "Librarian"
|
||||
content {
|
||||
includeGroupByRegex("^org\\.parchmentmc.*")
|
||||
includeGroup("net.neoforged")
|
||||
}
|
||||
}
|
||||
|
||||
@ -50,10 +42,9 @@ dependencies {
|
||||
implementation(libs.spotless)
|
||||
|
||||
implementation(libs.fabric.loom)
|
||||
implementation(libs.forgeGradle)
|
||||
implementation(libs.ideaExt)
|
||||
implementation(libs.librarian)
|
||||
implementation(libs.minotaur)
|
||||
implementation(libs.modDevGradle)
|
||||
implementation(libs.vanillaExtract)
|
||||
}
|
||||
|
||||
|
@ -10,21 +10,22 @@ import cc.tweaked.gradle.IdeaRunConfigurations
|
||||
import cc.tweaked.gradle.MinecraftConfigurations
|
||||
|
||||
plugins {
|
||||
id("net.minecraftforge.gradle")
|
||||
// We must apply java-convention after Forge, as we need the fg extension to be present.
|
||||
id("cc-tweaked.java-convention")
|
||||
id("org.parchmentmc.librarian.forgegradle")
|
||||
id("net.neoforged.moddev.legacyforge")
|
||||
}
|
||||
|
||||
plugins.apply(CCTweakedPlugin::class.java)
|
||||
|
||||
val mcVersion: String by extra
|
||||
|
||||
minecraft {
|
||||
legacyForge {
|
||||
val libs = project.extensions.getByType<VersionCatalogsExtension>().named("libs")
|
||||
mappings("parchment", "${libs.findVersion("parchmentMc").get()}-${libs.findVersion("parchment").get()}-$mcVersion")
|
||||
version = "${mcVersion}-${libs.findVersion("forge").get()}"
|
||||
|
||||
accessTransformer(project(":forge").file("src/main/resources/META-INF/accesstransformer.cfg"))
|
||||
parchment {
|
||||
minecraftVersion = libs.findVersion("parchmentMc").get().toString()
|
||||
mappingsVersion = libs.findVersion("parchment").get().toString()
|
||||
}
|
||||
}
|
||||
|
||||
MinecraftConfigurations.setup(project)
|
||||
@ -32,13 +33,3 @@ MinecraftConfigurations.setup(project)
|
||||
extensions.configure(CCTweakedExtension::class.java) {
|
||||
linters(minecraft = true, loader = "forge")
|
||||
}
|
||||
|
||||
dependencies {
|
||||
val libs = project.extensions.getByType<VersionCatalogsExtension>().named("libs")
|
||||
"minecraft"("net.minecraftforge:forge:$mcVersion-${libs.findVersion("forge").get()}")
|
||||
}
|
||||
|
||||
tasks.configureEach {
|
||||
// genIntellijRuns isn't registered until much later, so we need this silly hijinks.
|
||||
if (name == "genIntellijRuns") doLast { IdeaRunConfigurations(project).patch() }
|
||||
}
|
||||
|
@ -44,13 +44,6 @@ repositories {
|
||||
|
||||
exclusiveContent {
|
||||
forRepositories(mainMaven)
|
||||
|
||||
// Include the ForgeGradle repository if present. This requires that ForgeGradle is already present, which we
|
||||
// enforce in our Forge overlay.
|
||||
val fg =
|
||||
project.extensions.findByType(net.minecraftforge.gradle.userdev.DependencyManagementExtension::class.java)
|
||||
if (fg != null) forRepositories(fg.repository)
|
||||
|
||||
filter {
|
||||
includeGroup("cc.tweaked")
|
||||
// Things we mirror
|
||||
@ -99,6 +92,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(
|
||||
|
@ -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.
|
||||
|
||||
|
@ -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) }
|
||||
}
|
||||
|
||||
|
@ -1,26 +0,0 @@
|
||||
// SPDX-FileCopyrightText: 2023 The CC: Tweaked Developers
|
||||
//
|
||||
// SPDX-License-Identifier: MPL-2.0
|
||||
|
||||
package cc.tweaked.gradle
|
||||
|
||||
import net.minecraftforge.gradle.common.util.RunConfig
|
||||
import net.minecraftforge.gradle.common.util.runs.setRunConfigInternal
|
||||
import org.gradle.api.plugins.JavaPluginExtension
|
||||
import org.gradle.api.tasks.JavaExec
|
||||
import org.gradle.jvm.toolchain.JavaToolchainService
|
||||
import java.nio.file.Files
|
||||
|
||||
/**
|
||||
* Set [JavaExec] task to run a given [RunConfig].
|
||||
*/
|
||||
fun JavaExec.setRunConfig(config: RunConfig) {
|
||||
dependsOn("prepareRuns")
|
||||
setRunConfigInternal(project, this, config)
|
||||
doFirst("Create working directory") { Files.createDirectories(workingDir.toPath()) }
|
||||
|
||||
javaLauncher.set(
|
||||
project.extensions.getByType(JavaToolchainService::class.java)
|
||||
.launcherFor(project.extensions.getByType(JavaPluginExtension::class.java).toolchain),
|
||||
)
|
||||
}
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -4,7 +4,7 @@
|
||||
|
||||
package cc.tweaked.gradle
|
||||
|
||||
import net.minecraftforge.gradle.common.util.RunConfig
|
||||
import net.neoforged.moddevgradle.internal.RunGameTask
|
||||
import org.gradle.api.GradleException
|
||||
import org.gradle.api.file.FileSystemOperations
|
||||
import org.gradle.api.invocation.Gradle
|
||||
@ -65,11 +65,19 @@ abstract class ClientJavaExec : JavaExec() {
|
||||
setTestProperties()
|
||||
}
|
||||
|
||||
fun copyFromForge(path: String) = copyFromForge(project.tasks.getByName(path, RunGameTask::class))
|
||||
|
||||
/**
|
||||
* Set this task to run a given [RunConfig].
|
||||
* Set this task to run a given [RunGameTask].
|
||||
*/
|
||||
fun setRunConfig(config: RunConfig) {
|
||||
(this as JavaExec).setRunConfig(config)
|
||||
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.
|
||||
}
|
||||
|
||||
|
@ -1,51 +0,0 @@
|
||||
// SPDX-FileCopyrightText: 2023 The CC: Tweaked Developers
|
||||
//
|
||||
// SPDX-License-Identifier: MPL-2.0
|
||||
|
||||
package net.minecraftforge.gradle.common.util.runs
|
||||
|
||||
import net.minecraftforge.gradle.common.util.RunConfig
|
||||
import org.gradle.api.Project
|
||||
import org.gradle.process.CommandLineArgumentProvider
|
||||
import org.gradle.process.JavaExecSpec
|
||||
import java.io.File
|
||||
import java.util.function.Supplier
|
||||
import java.util.stream.Collectors
|
||||
import java.util.stream.Stream
|
||||
|
||||
/**
|
||||
* Set up a [JavaExecSpec] to execute a [RunConfig].
|
||||
*
|
||||
* [MinecraftRunTask] sets up all its properties when the task is executed, rather than when configured. As such, it's
|
||||
* not possible to use [cc.tweaked.gradle.copyToFull] like we do for Fabric. Instead, we set up the task manually.
|
||||
*
|
||||
* Unfortunately most of the functionality we need is package-private, and so we have to put our code into the package.
|
||||
*/
|
||||
internal fun setRunConfigInternal(project: Project, spec: JavaExecSpec, config: RunConfig) {
|
||||
spec.workingDir = File(config.workingDirectory)
|
||||
|
||||
spec.mainClass.set(config.main)
|
||||
for (source in config.allSources) spec.classpath(source.runtimeClasspath)
|
||||
|
||||
val originalTask = project.tasks.named(config.taskName, MinecraftRunTask::class.java)
|
||||
|
||||
// Add argument and JVM argument via providers, to be as lazy as possible with fetching artifacts.
|
||||
val lazyTokens = RunConfigGenerator.configureTokensLazy(
|
||||
project, config, RunConfigGenerator.mapModClassesToGradle(project, config),
|
||||
originalTask.get().minecraftArtifacts,
|
||||
originalTask.get().runtimeClasspathArtifacts,
|
||||
)
|
||||
spec.argumentProviders.add(
|
||||
CommandLineArgumentProvider {
|
||||
RunConfigGenerator.getArgsStream(config, lazyTokens, false).toList()
|
||||
},
|
||||
)
|
||||
spec.jvmArgumentProviders.add(
|
||||
CommandLineArgumentProvider {
|
||||
(if (config.isClient) config.jvmArgs + originalTask.get().additionalClientArgs.get() else config.jvmArgs).map { config.replace(lazyTokens, it) } +
|
||||
config.properties.map { (k, v) -> "-D${k}=${config.replace(lazyTokens, v)}" }
|
||||
},
|
||||
)
|
||||
|
||||
for ((key, value) in config.environment) spec.environment(key, config.replace(lazyTokens, value))
|
||||
}
|
@ -124,7 +124,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">
|
||||
|
@ -61,14 +61,13 @@ checkstyle = "10.14.1"
|
||||
errorProne-core = "2.27.0"
|
||||
errorProne-plugin = "3.1.0"
|
||||
fabric-loom = "1.7.1"
|
||||
forgeGradle = "6.0.21"
|
||||
githubRelease = "2.5.2"
|
||||
gradleVersions = "0.50.0"
|
||||
ideaExt = "1.1.7"
|
||||
illuaminate = "0.1.0-74-gf1551d5"
|
||||
librarian = "1.+"
|
||||
lwjgl = "3.3.3"
|
||||
minotaur = "2.+"
|
||||
minotaur = "2.8.7"
|
||||
modDevGradle = "2.0.74"
|
||||
nullAway = "0.10.25"
|
||||
shadow = "8.3.1"
|
||||
spotless = "6.23.3"
|
||||
@ -150,12 +149,11 @@ errorProne-core = { module = "com.google.errorprone:error_prone_core", version.r
|
||||
errorProne-plugin = { module = "net.ltgt.gradle:gradle-errorprone-plugin", version.ref = "errorProne-plugin" }
|
||||
errorProne-testHelpers = { module = "com.google.errorprone:error_prone_test_helpers", version.ref = "errorProne-core" }
|
||||
fabric-loom = { module = "net.fabricmc:fabric-loom", version.ref = "fabric-loom" }
|
||||
forgeGradle = { module = "net.minecraftforge.gradle:ForgeGradle", version.ref = "forgeGradle" }
|
||||
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" }
|
||||
librarian = { module = "org.parchmentmc:librarian", version.ref = "librarian" }
|
||||
minotaur = { module = "com.modrinth.minotaur:Minotaur", version.ref = "minotaur" }
|
||||
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" }
|
||||
@ -170,11 +168,9 @@ vanillaExtract = { module = "cc.tweaked.vanilla-extract:plugin", version.ref = "
|
||||
yarn = { module = "net.fabricmc:yarn", version.ref = "yarn" }
|
||||
|
||||
[plugins]
|
||||
forgeGradle = { id = "net.minecraftforge.gradle", version.ref = "forgeGradle" }
|
||||
githubRelease = { id = "com.github.breadmoirai.github-release", version.ref = "githubRelease" }
|
||||
gradleVersions = { id = "com.github.ben-manes.versions", version.ref = "gradleVersions" }
|
||||
kotlin = { id = "org.jetbrains.kotlin.jvm", version.ref = "kotlin" }
|
||||
librarian = { id = "org.parchmentmc.librarian.forgegradle", version.ref = "librarian" }
|
||||
shadow = { id = "com.gradleup.shadow", version.ref = "shadow" }
|
||||
taskTree = { id = "com.dorongold.task-tree", version.ref = "taskTree" }
|
||||
versionCatalogUpdate = { id = "nl.littlerobots.version-catalog-update", version.ref = "versionCatalogUpdate" }
|
||||
|
@ -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,15 @@ tasks.javadoc {
|
||||
<link href=" https://cdn.jsdelivr.net/npm/prismjs@1.29.0/themes/prism.min.css " rel="stylesheet">
|
||||
""".trimIndent(),
|
||||
)
|
||||
|
||||
taglets("cc.tweaked.javadoc.SnippetTaglet")
|
||||
tagletPath(configurations.detachedConfiguration(dependencies.project(":lints")).toList())
|
||||
|
||||
val snippetSources = listOf(":common", ":fabric", ":forge").flatMap {
|
||||
project(it).sourceSets["examples"].allSource.sourceDirectories
|
||||
}
|
||||
inputs.files(snippetSources)
|
||||
jFlags("-Dcc.snippet-path=" + snippetSources.joinToString(File.pathSeparator) { it.absolutePath })
|
||||
}
|
||||
|
||||
// Include the core-api in our javadoc export. This is wrong, but it means we can export a single javadoc dump.
|
||||
|
@ -22,7 +22,14 @@ import java.util.List;
|
||||
* <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.
|
||||
|
@ -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:
|
||||
*
|
||||
* <pre class="language language-java">{@code
|
||||
* 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);
|
||||
* });
|
||||
* }</pre>
|
||||
* {@snippet class=com.example.examplemod.ExampleAPI region=register}
|
||||
*
|
||||
* @param factory The factory for your API subclass.
|
||||
* @see ILuaAPIFactory
|
||||
|
@ -14,13 +14,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, on creates a {@link IPocketUpgrade} subclass and corresponding
|
||||
* {@link PocketUpgradeSerialiser} instance, which are then registered in a Forge registry.
|
||||
* {@link PocketUpgradeSerialiser} 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 registered internally. See the documentation in {@link PocketUpgradeSerialiser} for details on this process
|
||||
* and where files should be located.
|
||||
*
|
||||
* @see PocketUpgradeSerialiser For how to register a pocket computer upgrade.
|
||||
* the upgrade registered internally.
|
||||
*/
|
||||
public interface IPocketUpgrade extends UpgradeBase {
|
||||
/**
|
||||
|
@ -8,21 +8,69 @@ import dan200.computercraft.api.peripheral.IPeripheral;
|
||||
import dan200.computercraft.api.upgrades.UpgradeBase;
|
||||
import net.minecraft.core.Direction;
|
||||
import net.minecraft.nbt.CompoundTag;
|
||||
import net.minecraft.world.item.Items;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
import java.util.function.BiFunction;
|
||||
|
||||
/**
|
||||
* 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 TurtleUpgradeSerialiser} instance, which are then registered in a Forge registry.
|
||||
* {@link TurtleUpgradeSerialiser} 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 registered internally. See the documentation in {@link TurtleUpgradeSerialiser} for details on this process
|
||||
* and where files should be located.
|
||||
* the upgrade automatically registered.
|
||||
*
|
||||
* @see TurtleUpgradeSerialiser For how to register a turtle upgrade.
|
||||
* <h2>Example</h2>
|
||||
* <h3>Registering the upgrade serialiser</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.
|
||||
* <p>
|
||||
* {@snippet class=com.example.examplemod.ExampleTurtleUpgrade region=body}
|
||||
* <p>
|
||||
* Now we must construct a new upgrade serialiser. In most cases, you can use one of the helper methods
|
||||
* (e.g. {@link TurtleUpgradeSerialiser#simpleWithCustomItem(BiFunction)}), rather than defining your own implementation.
|
||||
*
|
||||
* {@snippet class=com.example.examplemod.ExampleMod region=turtle_upgrades}
|
||||
*
|
||||
* We now must register this upgrade serialiser. 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 serialiser.
|
||||
*
|
||||
* <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>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_upgrades/<my_upgrade_id>.json}.
|
||||
*
|
||||
* {@snippet file = data/examplemod/computercraft/turtle_upgrades/example_turtle_upgrade.json}
|
||||
*
|
||||
* The {@code "type"} field points to the ID of the upgrade serialiser we've just registered, while the other fields
|
||||
* are read by the serialiser itself. As our upgrade was defined with {@link TurtleUpgradeSerialiser#simpleWithCustomItem(BiFunction)}, the
|
||||
* {@code "item"} field will construct our upgrade with {@link Items#COMPASS}.
|
||||
* <p>
|
||||
* Rather than manually creating the file, it is recommended to data-generators to generate this file. This can be done
|
||||
* with {@link TurtleUpgradeDataProvider}.
|
||||
*
|
||||
* {@snippet class=com.example.examplemod.data.TurtleDataProvider region=body}
|
||||
*
|
||||
* @see TurtleUpgradeSerialiser Registering a turtle upgrade.
|
||||
*/
|
||||
public interface ITurtleUpgrade extends UpgradeBase {
|
||||
/**
|
||||
|
@ -29,6 +29,9 @@ import java.util.function.Consumer;
|
||||
* {@link #addUpgrades(Consumer)} function, construct each upgrade, and pass them off to the provided consumer to
|
||||
* generate them.
|
||||
*
|
||||
* <h2>Example</h2>
|
||||
* {@snippet class=com.example.examplemod.data.TurtleDataProvider region=body}
|
||||
*
|
||||
* @see TurtleUpgradeSerialiser
|
||||
*/
|
||||
public abstract class TurtleUpgradeDataProvider extends UpgradeDataProvider<ITurtleUpgrade, TurtleUpgradeSerialiser<?>> {
|
||||
|
@ -27,32 +27,6 @@ import java.util.function.Function;
|
||||
* If your turtle upgrade doesn't have any associated configurable parameters (like most upgrades), you can use
|
||||
* {@link #simple(Function)} or {@link #simpleWithCustomItem(BiFunction)} to create a basic upgrade serialiser.
|
||||
*
|
||||
* <h2>Example (Forge)</h2>
|
||||
* <pre class="language language-java">{@code
|
||||
* static final DeferredRegister<TurtleUpgradeSerialiser<?>> SERIALISERS = DeferredRegister.create( TurtleUpgradeSerialiser.TYPE, "my_mod" );
|
||||
*
|
||||
* // Register a new upgrade serialiser called "my_upgrade".
|
||||
* public static final RegistryObject<TurtleUpgradeSerialiser<MyUpgrade>> MY_UPGRADE =
|
||||
* SERIALISERS.register( "my_upgrade", () -> TurtleUpgradeSerialiser.simple( MyUpgrade::new ) );
|
||||
*
|
||||
* // Then in your constructor
|
||||
* SERIALISERS.register( bus );
|
||||
* }</pre>
|
||||
* <p>
|
||||
* We can then define a new upgrade using JSON by placing the following in
|
||||
* {@literal data/<my_mod>/computercraft/turtle_upgrades/<my_upgrade_id>.json}}.
|
||||
*
|
||||
* <pre class="language language-json">{@code
|
||||
* {
|
||||
* "type": "my_mod:my_upgrade",
|
||||
* }
|
||||
* }</pre>
|
||||
* <p>
|
||||
* Finally, we need to register a model for our upgrade. The way to do this varies on mod loader, see
|
||||
* {@link dan200.computercraft.api.client.turtle.TurtleUpgradeModeller} for more information.
|
||||
* <p>
|
||||
* {@link TurtleUpgradeDataProvider} provides a data provider to aid with generating these JSON files.
|
||||
*
|
||||
* @param <T> The type of turtle upgrade this is responsible for serialising.
|
||||
* @see ITurtleUpgrade
|
||||
* @see TurtleUpgradeDataProvider
|
||||
|
@ -32,6 +32,8 @@ import java.util.function.Function;
|
||||
*
|
||||
* @param <T> The base class of upgrades.
|
||||
* @param <R> The upgrade serialiser to register for.
|
||||
* @see dan200.computercraft.api.turtle.TurtleUpgradeDataProvider
|
||||
* @see dan200.computercraft.api.pocket.PocketUpgradeDataProvider
|
||||
*/
|
||||
public abstract class UpgradeDataProvider<T extends UpgradeBase, R extends UpgradeSerialiser<? extends T>> implements DataProvider {
|
||||
private final PackOutput output;
|
||||
@ -84,13 +86,9 @@ public abstract class UpgradeDataProvider<T extends UpgradeBase, R extends Upgra
|
||||
|
||||
/**
|
||||
* Add all turtle or pocket computer upgrades.
|
||||
* <p>
|
||||
* <strong>Example usage:</strong>
|
||||
* <pre class="language language-java">{@code
|
||||
* protected void addUpgrades(Consumer<Upgrade<TurtleUpgradeSerialiser<?>>> addUpgrade) {
|
||||
* simple(new ResourceLocation("mymod", "speaker"), SPEAKER_SERIALISER.get()).add(addUpgrade);
|
||||
* }
|
||||
* }</pre>
|
||||
*
|
||||
* <h4>Example</h4>
|
||||
* {@snippet class=com.example.examplemod.data.TurtleDataProvider region=body}
|
||||
*
|
||||
* @param addUpgrade A callback used to register an upgrade.
|
||||
*/
|
||||
|
68
projects/common-api/src/overview.html
Normal file
68
projects/common-api/src/overview.html
Normal 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>
|
@ -11,12 +11,6 @@ plugins {
|
||||
id("cc-tweaked.publishing")
|
||||
}
|
||||
|
||||
sourceSets {
|
||||
main {
|
||||
resources.srcDir("src/generated/resources")
|
||||
}
|
||||
}
|
||||
|
||||
minecraft {
|
||||
accessWideners(
|
||||
"src/main/resources/computercraft.accesswidener",
|
||||
@ -113,20 +107,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 }
|
||||
|
@ -0,0 +1,4 @@
|
||||
{
|
||||
"type": "examplemod:example_turtle_upgrade",
|
||||
"item": "minecraft:compass"
|
||||
}
|
@ -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
|
||||
}
|
@ -0,0 +1,39 @@
|
||||
package com.example.examplemod;
|
||||
|
||||
import com.example.examplemod.data.TurtleDataProvider;
|
||||
import com.example.examplemod.peripheral.FurnacePeripheral;
|
||||
import dan200.computercraft.api.ComputerCraftAPI;
|
||||
import dan200.computercraft.api.turtle.TurtleUpgradeSerialiser;
|
||||
|
||||
/**
|
||||
* 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 serialiser for our example turtle upgrade. See the documentation for {@link TurtleUpgradeSerialiser}
|
||||
* or {@code FabricExampleMod}/{@code ForgeExampleMod} for how this is registered.
|
||||
* <p>
|
||||
* This only defines the upgrade type. See {@link TurtleDataProvider} for defining the actual upgrade.
|
||||
*/
|
||||
// @start region=turtle_upgrades
|
||||
public static final TurtleUpgradeSerialiser<ExampleTurtleUpgrade> EXAMPLE_TURTLE_UPGRADE = TurtleUpgradeSerialiser.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();
|
||||
}
|
||||
}
|
@ -0,0 +1,17 @@
|
||||
package com.example.examplemod;
|
||||
|
||||
import dan200.computercraft.api.turtle.AbstractTurtleUpgrade;
|
||||
import dan200.computercraft.api.turtle.TurtleUpgradeType;
|
||||
import net.minecraft.resources.ResourceLocation;
|
||||
import net.minecraft.world.item.ItemStack;
|
||||
|
||||
/**
|
||||
* An example turtle upgrade.
|
||||
*/
|
||||
// @start region=body
|
||||
public class ExampleTurtleUpgrade extends AbstractTurtleUpgrade {
|
||||
public ExampleTurtleUpgrade(ResourceLocation id, ItemStack stack) {
|
||||
super(id, TurtleUpgradeType.PERIPHERAL, stack);
|
||||
}
|
||||
}
|
||||
// @end region=body
|
@ -0,0 +1,17 @@
|
||||
package com.example.examplemod.data;
|
||||
|
||||
import net.minecraft.data.DataGenerator;
|
||||
import net.minecraft.data.DataProvider;
|
||||
|
||||
/**
|
||||
* The entry point to example mod's data-generators.
|
||||
* <p>
|
||||
* This is called by our platform-specific entry-point (see {@code FabricExampleModDataGenerator} and
|
||||
* {@code ForgeExampleModDataGenerator}. That said, the exact setup isn't relevant (it will vary depending on
|
||||
* mod-loader), what's interesting is the contents of the {@link #run(DataGenerator.PackGenerator)} method!
|
||||
*/
|
||||
public final class ExampleModDataGenerators {
|
||||
public static void run(DataGenerator.PackGenerator pack) {
|
||||
pack.addProvider((DataProvider.Factory<?>) TurtleDataProvider::new);
|
||||
}
|
||||
}
|
@ -0,0 +1,34 @@
|
||||
package com.example.examplemod.data;
|
||||
|
||||
import com.example.examplemod.ExampleMod;
|
||||
import com.example.examplemod.ExampleTurtleUpgrade;
|
||||
import dan200.computercraft.api.turtle.TurtleUpgradeDataProvider;
|
||||
import dan200.computercraft.api.turtle.TurtleUpgradeSerialiser;
|
||||
import net.minecraft.data.PackOutput;
|
||||
import net.minecraft.resources.ResourceLocation;
|
||||
import net.minecraft.world.item.Items;
|
||||
|
||||
import java.util.function.Consumer;
|
||||
|
||||
/**
|
||||
* A {@link TurtleUpgradeDataProvider} that generates the JSON for our {@linkplain ExampleTurtleUpgrade example
|
||||
* upgrade}.
|
||||
*
|
||||
* @see ExampleModDataGenerators
|
||||
*/
|
||||
// @start region=body
|
||||
public class TurtleDataProvider extends TurtleUpgradeDataProvider {
|
||||
public TurtleDataProvider(PackOutput output) {
|
||||
super(output);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void addUpgrades(Consumer<Upgrade<TurtleUpgradeSerialiser<?>>> addUpgrade) {
|
||||
simpleWithCustomItem(
|
||||
new ResourceLocation(ExampleMod.MOD_ID, "example_turtle_upgrade"),
|
||||
ExampleMod.EXAMPLE_TURTLE_UPGRADE,
|
||||
Items.COMPASS
|
||||
).add(addUpgrade);
|
||||
}
|
||||
}
|
||||
// @end region=body
|
@ -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;
|
@ -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().getInt("Fuel");
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(@Nullable IPeripheral other) {
|
||||
return other instanceof BrewingStandPeripheral o && brewingStand == o.brewingStand;
|
||||
}
|
||||
}
|
||||
// @end region=body
|
@ -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
|
@ -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 new ResourceLocation(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().getInt("BurnTime");
|
||||
}
|
||||
}
|
||||
// @end region=body
|
@ -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
|
||||
|
@ -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>
|
||||
* <pre class="language language-java">{@code
|
||||
* @LuaFunction
|
||||
* public final void doSomething(Coerced<String> myString) {
|
||||
* var value = myString.value();
|
||||
* }
|
||||
* }</pre>
|
||||
* <h2>Example</h2>
|
||||
* {@snippet class=com.example.examplemod.ExampleAPI region=coerced}
|
||||
*
|
||||
* @param value The argument value.
|
||||
* @param <T> The type of the underlying value.
|
||||
|
@ -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:
|
||||
*
|
||||
* <pre class="language language-java">{@code
|
||||
* public class InventoryMethods implements GenericSource {
|
||||
* @LuaFunction(mainThread = true)
|
||||
* public int size(IItemHandler inventory) {
|
||||
* return inventory.getSlots();
|
||||
* }
|
||||
*
|
||||
* // ...
|
||||
* }
|
||||
* }</pre>
|
||||
* <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)
|
||||
*/
|
||||
|
@ -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,
|
||||
|
@ -27,21 +27,7 @@ import java.util.function.Consumer;
|
||||
*
|
||||
* <h2>Example</h2>
|
||||
*
|
||||
* <pre class="language language-java">{@code
|
||||
* 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);
|
||||
* }
|
||||
* }
|
||||
* }</pre>
|
||||
* {@snippet class=com.example.examplemod.peripheral.ComputerTrackingPeripheral region=body}
|
||||
*
|
||||
* @see IComputerAccess
|
||||
* @see IPeripheral#attach(IComputerAccess)
|
||||
|
@ -48,35 +48,11 @@
|
||||
* 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.
|
||||
*
|
||||
* <pre class="language language-java">{@code
|
||||
* import dan200.computercraft.api.lua.LuaFunction;
|
||||
* import dan200.computercraft.api.peripheral.GenericPeripheral;
|
||||
* import net.minecraft.world.level.block.entity.AbstractFurnaceBlockEntity;
|
||||
* {@snippet class=com.example.examplemod.peripheral.FurnacePeripheral region=body}
|
||||
*
|
||||
* public final class FurnacePeripheral implements GenericPeripheral {
|
||||
* @Override
|
||||
* public String id() {
|
||||
* return "mymod:furnace";
|
||||
* }
|
||||
*
|
||||
* @LuaFunction(mainThread = true)
|
||||
* public int getBurnTime(AbstractFurnaceBlockEntity furnace) {
|
||||
* return furnace.litTime;
|
||||
* }
|
||||
* }
|
||||
* }</pre>
|
||||
* <p>
|
||||
* Finally, we need to register our peripheral, so that ComputerCraft is aware of it:
|
||||
*
|
||||
* <pre class="language language-java">{@code
|
||||
* import dan200.computercraft.api.ComputerCraftAPI;
|
||||
*
|
||||
* public class ComputerCraftCompat {
|
||||
* public static void register() {
|
||||
* ComputerCraftAPI.registerGenericSource(new FurnacePeripheral());
|
||||
* }
|
||||
* }
|
||||
* }</pre>
|
||||
* {@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
|
||||
@ -85,36 +61,8 @@
|
||||
* 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}.
|
||||
*
|
||||
* <pre class="language language-java">{@code
|
||||
* 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;
|
||||
* {@snippet class=com.example.examplemod.peripheral.BrewingStandPeripheral region=body}
|
||||
*
|
||||
* public class FurnacePeripheral implements IPeripheral {
|
||||
* private final AbstractFurnaceBlockEntity furnace;
|
||||
*
|
||||
* 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;
|
||||
* }
|
||||
* }
|
||||
* }</pre>
|
||||
* <p>
|
||||
* Finally, we'll need to register our peripheral. This is done with capabilities on Forge, or the block lookup API on
|
||||
* Fabric.
|
||||
*
|
||||
@ -124,82 +72,11 @@
|
||||
* {@code ICapabilityProvider}. If you've got an existing system for dealing with this, we recommend you use that,
|
||||
* otherwise you can use something similar to the code below:
|
||||
*
|
||||
* <pre class="language language-java">{@code
|
||||
* import dan200.computercraft.api.peripheral.IPeripheral;
|
||||
* import net.minecraft.core.Direction;
|
||||
* import net.minecraft.resources.ResourceLocation;
|
||||
* import net.minecraft.world.level.block.entity.AbstractFurnaceBlockEntity;
|
||||
* import net.minecraft.world.level.block.entity.BlockEntity;
|
||||
* import net.minecraftforge.common.capabilities.Capability;
|
||||
* import net.minecraftforge.common.capabilities.CapabilityManager;
|
||||
* import net.minecraftforge.common.capabilities.CapabilityToken;
|
||||
* import net.minecraftforge.common.capabilities.ICapabilityProvider;
|
||||
* import net.minecraftforge.common.util.LazyOptional;
|
||||
* import net.minecraftforge.event.AttachCapabilitiesEvent;
|
||||
* import org.jetbrains.annotations.Nullable;
|
||||
*
|
||||
* import java.util.function.Function;
|
||||
*
|
||||
* public class ComputerCraftCompat {
|
||||
* public static final Capability<IPeripheral> CAPABILITY_PERIPHERAL = CapabilityManager.get(new CapabilityToken<>() {
|
||||
* });
|
||||
* private static final ResourceLocation PERIPHERAL = new ResourceLocation("mymod", "peripheral");
|
||||
*
|
||||
* public static void register(AttachCapabilitiesEvent<BlockEntity> event) {
|
||||
* if (event.getObject() instanceof AbstractFurnaceBlockEntity furnace) {
|
||||
* PeripheralProvider.attach(event, furnace, FurnacePeripheral::new);
|
||||
* }
|
||||
* }
|
||||
*
|
||||
* // A {@link ICapabilityProvider} that lazily creates an {@link IPeripheral} when required.
|
||||
* private static class PeripheralProvider<O extends BlockEntity> implements ICapabilityProvider {
|
||||
* private final O blockEntity;
|
||||
* private final Function<O, IPeripheral> factory;
|
||||
* private @Nullable LazyOptional<IPeripheral> peripheral;
|
||||
*
|
||||
* private PeripheralProvider(O blockEntity, Function<O, IPeripheral> factory) {
|
||||
* this.blockEntity = blockEntity;
|
||||
* this.factory = factory;
|
||||
* }
|
||||
*
|
||||
* private static <O extends BlockEntity> void attach(AttachCapabilitiesEvent<BlockEntity> event, O blockEntity, Function<O, IPeripheral> factory) {
|
||||
* var provider = new PeripheralProvider<>(blockEntity, factory);
|
||||
* event.addCapability(PERIPHERAL, provider);
|
||||
* event.addListener(provider::invalidate);
|
||||
* }
|
||||
*
|
||||
* private void invalidate() {
|
||||
* if (peripheral != null) peripheral.invalidate();
|
||||
* peripheral = null;
|
||||
* }
|
||||
*
|
||||
* @Override
|
||||
* public <T> LazyOptional<T> getCapability(Capability<T> capability, @Nullable Direction direction) {
|
||||
* if (capability != CAPABILITY_PERIPHERAL) return LazyOptional.empty();
|
||||
* if (blockEntity.isRemoved()) return LazyOptional.empty();
|
||||
*
|
||||
* var peripheral = this.peripheral;
|
||||
* return (peripheral == null ? (this.peripheral = LazyOptional.of(() -> factory.apply(blockEntity))) : peripheral).cast();
|
||||
* }
|
||||
* }
|
||||
* }
|
||||
* }</pre>
|
||||
* {@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}.
|
||||
*
|
||||
* <pre class="language language-java">{@code
|
||||
* 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);
|
||||
* }
|
||||
* }
|
||||
* }</pre>
|
||||
* {@snippet class=com.example.examplemod.FabricExampleMod region=peripherals}
|
||||
*/
|
||||
package dan200.computercraft.api.peripheral;
|
||||
|
@ -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>
|
||||
|
@ -7,10 +7,6 @@ plugins {
|
||||
id("cc-tweaked.publishing")
|
||||
}
|
||||
|
||||
java {
|
||||
withJavadocJar()
|
||||
}
|
||||
|
||||
cct.inlineProject(":common-api")
|
||||
|
||||
dependencies {
|
||||
|
@ -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.defaultRefmapName.set("computercraft.refmap.json")
|
||||
@ -126,6 +124,10 @@ loom {
|
||||
sourceSet(sourceSets.testMod.get())
|
||||
sourceSet(project(":common").sourceSets.testMod.get())
|
||||
}
|
||||
|
||||
register("examplemod") {
|
||||
sourceSet(sourceSets.examples.get())
|
||||
}
|
||||
}
|
||||
|
||||
runs {
|
||||
@ -142,19 +144,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
|
||||
@ -169,7 +176,7 @@ loom {
|
||||
val testClient by registering {
|
||||
configName = "Test Client"
|
||||
client()
|
||||
configureForGameTest(this)
|
||||
configureForGameTest()
|
||||
|
||||
runDir("run/testClient")
|
||||
property("cctest.tags", "client,common")
|
||||
@ -178,16 +185,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())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -0,0 +1,30 @@
|
||||
package com.example.examplemod;
|
||||
|
||||
import com.example.examplemod.peripheral.BrewingStandPeripheral;
|
||||
import dan200.computercraft.api.peripheral.PeripheralLookup;
|
||||
import dan200.computercraft.api.turtle.TurtleUpgradeSerialiser;
|
||||
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<TurtleUpgradeSerialiser<?>>) BuiltInRegistries.REGISTRY.get(TurtleUpgradeSerialiser.registryId().location());
|
||||
Registry.register(turtleUpgradeSerialisers, new ResourceLocation(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
|
||||
}
|
||||
}
|
@ -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
|
||||
}
|
||||
}
|
@ -0,0 +1,17 @@
|
||||
package com.example.examplemod;
|
||||
|
||||
import com.example.examplemod.data.TurtleDataProvider;
|
||||
import net.fabricmc.fabric.api.datagen.v1.DataGeneratorEntrypoint;
|
||||
import net.fabricmc.fabric.api.datagen.v1.FabricDataGenerator;
|
||||
import net.minecraft.data.DataProvider;
|
||||
|
||||
/**
|
||||
* The data generator entrypoint for our Fabric example mod.
|
||||
*/
|
||||
public class FabricExampleModDataGenerator implements DataGeneratorEntrypoint {
|
||||
@Override
|
||||
public void onInitializeDataGenerator(FabricDataGenerator generator) {
|
||||
var pack = generator.createPack();
|
||||
pack.addProvider((DataProvider.Factory<?>) TurtleDataProvider::new);
|
||||
}
|
||||
}
|
16
projects/fabric/src/examples/resources/fabric.mod.json
Normal file
16
projects/fabric/src/examples/resources/fabric.mod.json
Normal 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": "*"
|
||||
}
|
||||
}
|
@ -7,10 +7,6 @@ plugins {
|
||||
id("cc-tweaked.publishing")
|
||||
}
|
||||
|
||||
java {
|
||||
withJavadocJar()
|
||||
}
|
||||
|
||||
cct.inlineProject(":common-api")
|
||||
|
||||
dependencies {
|
||||
@ -20,11 +16,3 @@ dependencies {
|
||||
tasks.javadoc {
|
||||
include("dan200/computercraft/api/**/*.java")
|
||||
}
|
||||
|
||||
publishing {
|
||||
publications {
|
||||
named("maven", MavenPublication::class) {
|
||||
fg.component(this)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -3,7 +3,7 @@
|
||||
// SPDX-License-Identifier: MPL-2.0
|
||||
|
||||
import cc.tweaked.gradle.*
|
||||
import net.minecraftforge.gradle.common.util.RunConfig
|
||||
import net.neoforged.moddevgradle.dsl.RunModel
|
||||
|
||||
plugins {
|
||||
id("cc-tweaked.forge")
|
||||
@ -19,105 +19,122 @@ cct {
|
||||
allProjects.forEach { externalSources(it) }
|
||||
}
|
||||
|
||||
sourceSets {
|
||||
main {
|
||||
resources.srcDir("src/generated/resources")
|
||||
legacyForge {
|
||||
val computercraft by mods.registering {
|
||||
cct.sourceDirectories.get().forEach {
|
||||
if (it.classes) sourceSet(it.sourceSet)
|
||||
}
|
||||
}
|
||||
|
||||
val computercraftDatagen by mods.registering {
|
||||
cct.sourceDirectories.get().forEach {
|
||||
if (it.classes) sourceSet(it.sourceSet)
|
||||
}
|
||||
sourceSet(sourceSets.datagen.get())
|
||||
}
|
||||
|
||||
val testMod by mods.registering {
|
||||
sourceSet(sourceSets.testMod.get())
|
||||
sourceSet(sourceSets.testFixtures.get())
|
||||
sourceSet(project(":core").sourceSets["testFixtures"])
|
||||
}
|
||||
|
||||
val exampleMod by mods.registering {
|
||||
sourceSet(sourceSets.examples.get())
|
||||
}
|
||||
}
|
||||
|
||||
minecraft {
|
||||
runs {
|
||||
// configureEach would be better, but we need to eagerly configure configs or otherwise the run task doesn't
|
||||
// get set up properly.
|
||||
all {
|
||||
property("forge.logging.markers", "REGISTRIES")
|
||||
property("forge.logging.console.level", "debug")
|
||||
|
||||
mods.register("computercraft") {
|
||||
cct.sourceDirectories.get().forEach {
|
||||
if (it.classes) sources(it.sourceSet)
|
||||
}
|
||||
}
|
||||
configureEach {
|
||||
ideName = "Forge - ${name.capitalise()}"
|
||||
systemProperty("forge.logging.markers", "REGISTRIES")
|
||||
systemProperty("forge.logging.console.level", "debug")
|
||||
loadedMods.add(computercraft)
|
||||
}
|
||||
|
||||
val client by registering {
|
||||
workingDirectory(file("run"))
|
||||
register("client") {
|
||||
client()
|
||||
}
|
||||
|
||||
val server by registering {
|
||||
workingDirectory(file("run/server"))
|
||||
arg("--nogui")
|
||||
register("server") {
|
||||
server()
|
||||
gameDirectory = file("run/server")
|
||||
programArgument("--nogui")
|
||||
}
|
||||
|
||||
val data by registering {
|
||||
workingDirectory(file("run"))
|
||||
args(
|
||||
"--mod", "computercraft", "--all",
|
||||
"--output", layout.buildDirectory.dir("generatedResources").getAbsolutePath(),
|
||||
"--existing", project(":common").file("src/main/resources/"),
|
||||
"--existing", file("src/main/resources/"),
|
||||
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,
|
||||
)
|
||||
|
||||
mods.named("computercraft") {
|
||||
source(sourceSets["datagen"])
|
||||
}
|
||||
programArgument("--mixin.config=computercraft-gametest.mixins.json")
|
||||
loadedMods.add(testMod)
|
||||
|
||||
jvmArgument("-ea")
|
||||
}
|
||||
|
||||
fun RunConfig.configureForGameTest() {
|
||||
val old = lazyTokens["minecraft_classpath"]
|
||||
lazyToken("minecraft_classpath") {
|
||||
// Add all files in testMinecraftLibrary to the classpath.
|
||||
val allFiles = mutableSetOf<String>()
|
||||
|
||||
val oldVal = old?.get()
|
||||
if (!oldVal.isNullOrEmpty()) allFiles.addAll(oldVal.split(File.pathSeparatorChar))
|
||||
|
||||
for (file in configurations["testMinecraftLibrary"].resolve()) allFiles.add(file.absolutePath)
|
||||
|
||||
allFiles.joinToString(File.pathSeparator)
|
||||
}
|
||||
|
||||
property("cctest.sources", project(":common").file("src/testMod/resources/data/cctest").absolutePath)
|
||||
|
||||
arg("--mixin.config=computercraft-gametest.mixins.json")
|
||||
|
||||
mods.register("cctest") {
|
||||
source(sourceSets["testMod"])
|
||||
source(sourceSets["testFixtures"])
|
||||
source(project(":core").sourceSets["testFixtures"])
|
||||
}
|
||||
}
|
||||
|
||||
val testClient by registering {
|
||||
workingDirectory(file("run/testClient"))
|
||||
parent(client.get())
|
||||
register("testClient") {
|
||||
client()
|
||||
gameDirectory = file("run/testClient")
|
||||
configureForGameTest()
|
||||
|
||||
property("cctest.tags", "client,common")
|
||||
systemProperty("cctest.tags", "client,common")
|
||||
}
|
||||
|
||||
val gameTestServer by registering {
|
||||
workingDirectory(file("run/testServer"))
|
||||
register("gametest") {
|
||||
type = "gameTestServer"
|
||||
configureForGameTest()
|
||||
|
||||
property("forge.logging.console.level", "info")
|
||||
jvmArg("-ea")
|
||||
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 {
|
||||
minecraftLibrary { extendsFrom(minecraftEmbed.get()) }
|
||||
additionalRuntimeClasspath { extendsFrom(jarJar.get()) }
|
||||
|
||||
// Move minecraftLibrary/minecraftEmbed out of implementation, and into runtimeOnly.
|
||||
implementation { setExtendsFrom(extendsFrom - setOf(minecraftLibrary.get(), minecraftEmbed.get())) }
|
||||
runtimeOnly { extendsFrom(minecraftLibrary.get(), minecraftEmbed.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()) }
|
||||
}
|
||||
}
|
||||
|
||||
@ -126,31 +143,22 @@ dependencies {
|
||||
annotationProcessorEverywhere(libs.autoService)
|
||||
|
||||
clientCompileOnly(variantOf(libs.emi) { classifier("api") })
|
||||
libs.bundles.externalMods.forge.compile.get().map { compileOnly(fg.deobf(it)) }
|
||||
libs.bundles.externalMods.forge.runtime.get().map { runtimeOnly(fg.deobf(it)) }
|
||||
|
||||
// fg.debof only accepts a closure to configure the dependency, so doesn't work with Kotlin. We create and configure
|
||||
// the dep first, and then pass it off to ForgeGradle.
|
||||
(create(variantOf(libs.create.forge) { classifier("slim") }.get()) as ExternalModuleDependency)
|
||||
.apply { isTransitive = false }.let { compileOnly(fg.deobf(it)) }
|
||||
modCompileOnly(libs.bundles.externalMods.forge.compile)
|
||||
modRuntimeOnly(libs.bundles.externalMods.forge.runtime)
|
||||
modCompileOnly(variantOf(libs.create.forge) { classifier("slim") })
|
||||
|
||||
// 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) {
|
||||
val version = libs.versions.cobalt.get()
|
||||
jarJar.ranged(this, "[$version,${getNextVersion(version)})")
|
||||
}
|
||||
minecraftEmbed(libs.jzlib) {
|
||||
jarJar.ranged(this, "[${libs.versions.jzlib.get()},)")
|
||||
}
|
||||
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) { isTransitive = false }
|
||||
minecraftLibrary(libs.netty.socks) { isTransitive = false }
|
||||
minecraftLibrary(libs.netty.proxy) { isTransitive = false }
|
||||
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 +171,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")))
|
||||
}
|
||||
@ -181,23 +189,6 @@ tasks.processResources {
|
||||
}
|
||||
|
||||
tasks.jar {
|
||||
finalizedBy("reobfJar")
|
||||
archiveClassifier.set("slim")
|
||||
|
||||
for (source in cct.sourceDirectories.get()) {
|
||||
if (source.classes && source.external) from(source.sourceSet.output)
|
||||
}
|
||||
}
|
||||
|
||||
tasks.sourcesJar {
|
||||
for (source in cct.sourceDirectories.get()) from(source.sourceSet.allSource)
|
||||
}
|
||||
|
||||
tasks.jarJar {
|
||||
finalizedBy("reobfJarJar")
|
||||
archiveClassifier.set("")
|
||||
duplicatesStrategy = DuplicatesStrategy.FAIL
|
||||
|
||||
// Include all classes from other projects except core.
|
||||
val coreSources = project(":core").sourceSets["main"]
|
||||
for (source in cct.sourceDirectories.get()) {
|
||||
@ -210,7 +201,9 @@ tasks.jarJar {
|
||||
}
|
||||
}
|
||||
|
||||
tasks.assemble { dependsOn("jarJar") }
|
||||
tasks.sourcesJar {
|
||||
for (source in cct.sourceDirectories.get()) from(source.sourceSet.allSource)
|
||||
}
|
||||
|
||||
// Check tasks
|
||||
|
||||
@ -218,22 +211,15 @@ 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))
|
||||
|
||||
setRunConfig(minecraft.runs["gameTestServer"])
|
||||
|
||||
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"
|
||||
setRunConfig(minecraft.runs["testClient"])
|
||||
copyFromForge("runTestClient")
|
||||
tags("client")
|
||||
}
|
||||
cct.jacoco(runGametestClient)
|
||||
@ -247,22 +233,12 @@ tasks.register("checkClient") {
|
||||
// Upload tasks
|
||||
|
||||
modPublishing {
|
||||
output.set(tasks.jarJar)
|
||||
}
|
||||
|
||||
// Don't publish the slim jar
|
||||
for (cfg in listOf(configurations.apiElements, configurations.runtimeElements)) {
|
||||
cfg.configure { artifacts.removeIf { it.classifier == "slim" } }
|
||||
output.set(tasks.reobfJar)
|
||||
}
|
||||
|
||||
publishing {
|
||||
publications {
|
||||
named("maven", MavenPublication::class) {
|
||||
fg.component(this)
|
||||
// jarJar.component is broken (https://github.com/MinecraftForge/ForgeGradle/issues/914), so declare the
|
||||
// artifact explicitly.
|
||||
artifact(tasks.jarJar)
|
||||
|
||||
mavenDependencies {
|
||||
cct.configureExcludes(this)
|
||||
exclude(libs.jei.forge.get())
|
||||
|
@ -0,0 +1,92 @@
|
||||
package com.example.examplemod;
|
||||
|
||||
import com.example.examplemod.peripheral.BrewingStandPeripheral;
|
||||
import dan200.computercraft.api.peripheral.IPeripheral;
|
||||
import dan200.computercraft.api.turtle.TurtleUpgradeSerialiser;
|
||||
import net.minecraft.core.Direction;
|
||||
import net.minecraft.resources.ResourceLocation;
|
||||
import net.minecraft.world.level.block.entity.BlockEntity;
|
||||
import net.minecraft.world.level.block.entity.BrewingStandBlockEntity;
|
||||
import net.minecraftforge.common.MinecraftForge;
|
||||
import net.minecraftforge.common.capabilities.Capability;
|
||||
import net.minecraftforge.common.capabilities.CapabilityManager;
|
||||
import net.minecraftforge.common.capabilities.CapabilityToken;
|
||||
import net.minecraftforge.common.capabilities.ICapabilityProvider;
|
||||
import net.minecraftforge.common.util.LazyOptional;
|
||||
import net.minecraftforge.event.AttachCapabilitiesEvent;
|
||||
import net.minecraftforge.fml.common.Mod;
|
||||
import net.minecraftforge.fml.event.lifecycle.FMLCommonSetupEvent;
|
||||
import net.minecraftforge.fml.javafmlmod.FMLJavaModLoadingContext;
|
||||
import net.minecraftforge.registries.RegisterEvent;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
import java.util.function.Function;
|
||||
|
||||
/**
|
||||
* The main entry point for the Forge version of our example mod.
|
||||
*/
|
||||
@Mod(ExampleMod.MOD_ID)
|
||||
public class ForgeExampleMod {
|
||||
public ForgeExampleMod() {
|
||||
// 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
|
||||
var modBus = FMLJavaModLoadingContext.get().getModEventBus();
|
||||
modBus.addListener((RegisterEvent event) -> {
|
||||
event.register(TurtleUpgradeSerialiser.registryId(), new ResourceLocation(ExampleMod.MOD_ID, "example_turtle_upgrade"), () -> ExampleMod.EXAMPLE_TURTLE_UPGRADE);
|
||||
});
|
||||
// @end region=turtle_upgrades
|
||||
|
||||
modBus.addListener((FMLCommonSetupEvent event) -> ExampleMod.registerComputerCraft());
|
||||
|
||||
MinecraftForge.EVENT_BUS.addGenericListener(BlockEntity.class, ForgeExampleMod::attachPeripherals);
|
||||
}
|
||||
|
||||
// @start region=peripherals
|
||||
// The main function to attach peripherals to block entities. This should be added to the Forge event bus.
|
||||
public static void attachPeripherals(AttachCapabilitiesEvent<BlockEntity> event) {
|
||||
if (event.getObject() instanceof BrewingStandBlockEntity brewingStand) {
|
||||
PeripheralProvider.attach(event, brewingStand, BrewingStandPeripheral::new);
|
||||
}
|
||||
}
|
||||
|
||||
// Boilerplate for adding a new capability provider
|
||||
|
||||
public static final Capability<IPeripheral> CAPABILITY_PERIPHERAL = CapabilityManager.get(new CapabilityToken<>() {
|
||||
});
|
||||
private static final ResourceLocation PERIPHERAL = new ResourceLocation(ExampleMod.MOD_ID, "peripheral");
|
||||
|
||||
// A {@link ICapabilityProvider} that lazily creates an {@link IPeripheral} when required.
|
||||
private static final class PeripheralProvider<O extends BlockEntity> implements ICapabilityProvider {
|
||||
private final O blockEntity;
|
||||
private final Function<O, IPeripheral> factory;
|
||||
private @Nullable LazyOptional<IPeripheral> peripheral;
|
||||
|
||||
private PeripheralProvider(O blockEntity, Function<O, IPeripheral> factory) {
|
||||
this.blockEntity = blockEntity;
|
||||
this.factory = factory;
|
||||
}
|
||||
|
||||
private static <O extends BlockEntity> void attach(AttachCapabilitiesEvent<BlockEntity> event, O blockEntity, Function<O, IPeripheral> factory) {
|
||||
var provider = new PeripheralProvider<>(blockEntity, factory);
|
||||
event.addCapability(PERIPHERAL, provider);
|
||||
event.addListener(provider::invalidate);
|
||||
}
|
||||
|
||||
private void invalidate() {
|
||||
if (peripheral != null) peripheral.invalidate();
|
||||
peripheral = null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> LazyOptional<T> getCapability(Capability<T> capability, @Nullable Direction direction) {
|
||||
if (capability != CAPABILITY_PERIPHERAL) return LazyOptional.empty();
|
||||
if (blockEntity.isRemoved()) return LazyOptional.empty();
|
||||
|
||||
var peripheral = this.peripheral;
|
||||
return (peripheral == null ? (this.peripheral = LazyOptional.of(() -> factory.apply(blockEntity))) : peripheral).cast();
|
||||
}
|
||||
}
|
||||
// @end region=peripherals
|
||||
}
|
@ -0,0 +1,20 @@
|
||||
package com.example.examplemod;
|
||||
|
||||
import dan200.computercraft.api.client.turtle.RegisterTurtleModellersEvent;
|
||||
import dan200.computercraft.api.client.turtle.TurtleUpgradeModeller;
|
||||
import net.minecraftforge.api.distmarker.Dist;
|
||||
import net.minecraftforge.eventbus.api.SubscribeEvent;
|
||||
import net.minecraftforge.fml.common.Mod;
|
||||
|
||||
/**
|
||||
* The client-side entry point for the Forge version of our example mod.
|
||||
*/
|
||||
@Mod.EventBusSubscriber(modid = ExampleMod.MOD_ID, value = Dist.CLIENT, bus = Mod.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
|
||||
}
|
@ -0,0 +1,19 @@
|
||||
package com.example.examplemod;
|
||||
|
||||
import com.example.examplemod.data.ExampleModDataGenerators;
|
||||
import net.minecraftforge.data.event.GatherDataEvent;
|
||||
import net.minecraftforge.eventbus.api.SubscribeEvent;
|
||||
import net.minecraftforge.fml.common.Mod;
|
||||
|
||||
/**
|
||||
* The data generator entrypoint for the Forge version of our example mod.
|
||||
*
|
||||
* @see ExampleModDataGenerators The main implementation
|
||||
*/
|
||||
@Mod.EventBusSubscriber(bus = Mod.EventBusSubscriber.Bus.MOD)
|
||||
public class ForgeExampleModDataGenerator {
|
||||
@SubscribeEvent
|
||||
public static void gather(GatherDataEvent event) {
|
||||
ExampleModDataGenerators.run(event.getGenerator().getVanillaPack(true));
|
||||
}
|
||||
}
|
14
projects/forge/src/examples/resources/META-INF/mods.toml
Normal file
14
projects/forge/src/examples/resources/META-INF/mods.toml
Normal 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"
|
6
projects/forge/src/examples/resources/pack.mcmeta
Normal file
6
projects/forge/src/examples/resources/pack.mcmeta
Normal file
@ -0,0 +1,6 @@
|
||||
{
|
||||
"pack": {
|
||||
"pack_format": 15,
|
||||
"description": "Example Mod"
|
||||
}
|
||||
}
|
@ -0,0 +1,138 @@
|
||||
// SPDX-FileCopyrightText: 2025 The CC: Tweaked Developers
|
||||
//
|
||||
// SPDX-License-Identifier: MPL-2.0
|
||||
|
||||
package cc.tweaked.javadoc
|
||||
|
||||
import com.sun.source.doctree.DocTree
|
||||
import com.sun.source.doctree.TextTree
|
||||
import com.sun.source.doctree.UnknownInlineTagTree
|
||||
import com.sun.source.util.DocTreePath
|
||||
import jdk.javadoc.doclet.*
|
||||
import java.io.File
|
||||
import java.nio.file.Files
|
||||
import java.nio.file.Path
|
||||
import java.util.*
|
||||
import java.util.regex.Pattern
|
||||
import javax.lang.model.element.Element
|
||||
import javax.tools.Diagnostic
|
||||
import kotlin.io.path.extension
|
||||
|
||||
/**
|
||||
* A primitive reimplementation of Java 21's `@snippet` tag. This only supports including external snippets via `file`
|
||||
* and `class`, and not the inline body.
|
||||
*/
|
||||
class SnippetTaglet : Taglet {
|
||||
override fun getName(): String = "snippet"
|
||||
override fun isInlineTag(): Boolean = true
|
||||
override fun getAllowedLocations(): Set<Taglet.Location> = locations
|
||||
|
||||
private lateinit var env: DocletEnvironment
|
||||
private lateinit var reporter: Reporter
|
||||
private lateinit var snippetPath: List<File>
|
||||
|
||||
override fun init(env: DocletEnvironment, doclet: Doclet) {
|
||||
super.init(env, doclet)
|
||||
this.env = env
|
||||
reporter = (doclet as StandardDoclet).reporter
|
||||
|
||||
this.snippetPath =
|
||||
System.getProperty("cc.snippet-path")?.split(File.pathSeparatorChar)?.map { File(it) } ?: emptyList()
|
||||
}
|
||||
|
||||
/** Parse our attributes into a key/value map */
|
||||
private fun parseAttributes(contents: String): Map<String, String> {
|
||||
val attributes = mutableMapOf<String, String>()
|
||||
val attributeMatcher = attribute.matcher(contents)
|
||||
var lastIndex = 0
|
||||
while (attributeMatcher.find()) {
|
||||
val key = attributeMatcher.group(1)
|
||||
val value = attributeMatcher.group(2)
|
||||
if (attributes.contains(key)) throw SnippetException("Duplicate attribute '$key'")
|
||||
attributes[key] = value
|
||||
lastIndex = attributeMatcher.end()
|
||||
}
|
||||
|
||||
while (lastIndex < contents.length) {
|
||||
val c = contents[lastIndex]
|
||||
if (c != ' ') throw SnippetException("Unexpected '$c'")
|
||||
}
|
||||
|
||||
return attributes
|
||||
}
|
||||
|
||||
/** Locate our snippet file within the [snippetPath] */
|
||||
private fun findSnippetFile(fileName: String): Path = snippetPath.firstNotNullOfOrNull {
|
||||
val found = it.resolve(fileName)
|
||||
if (found.exists()) found.toPath() else null
|
||||
} ?: throw SnippetException("Cannot find file '$fileName'")
|
||||
|
||||
private fun processInlineTag(tag: UnknownInlineTagTree): String {
|
||||
val tagContent = tag.content
|
||||
if (tagContent.size != 1 || tagContent[0].kind != DocTree.Kind.TEXT) throw SnippetException("Expected a single text node")
|
||||
val attributes = parseAttributes((tagContent[0] as TextTree).body)
|
||||
|
||||
val hasFile = attributes.contains("file")
|
||||
val hasClass = attributes.contains("class")
|
||||
if (hasFile && hasClass) throw SnippetException("Cannot specify file and class")
|
||||
|
||||
val file = when {
|
||||
hasFile -> findSnippetFile(attributes["file"]!!)
|
||||
hasClass -> findSnippetFile(attributes["class"]!!.replace('.', '/') + ".java")
|
||||
else -> throw SnippetException("Snippet has no contents (must have file or class)")
|
||||
}
|
||||
|
||||
// And generate our snippet
|
||||
var snippetContents = Files.readString(file)
|
||||
|
||||
val region = attributes["region"]
|
||||
if (region != null) {
|
||||
val matcher =
|
||||
Pattern.compile("// @start region=" + Pattern.quote(region) + "\n(.*)\\s*// @end region=" + Pattern.quote(region), Pattern.DOTALL)
|
||||
.matcher(snippetContents)
|
||||
if (!matcher.find()) throw SnippetException("Cannot find region '$region'")
|
||||
snippetContents = matcher.group(1).trimIndent()
|
||||
}
|
||||
|
||||
return makeSnippet(file.extension, snippetContents)
|
||||
}
|
||||
|
||||
override fun toString(tags: List<DocTree>, element: Element): String {
|
||||
if (tags.size != 1) throw IllegalArgumentException("Tags should be length 1")
|
||||
val tag = tags[0] as UnknownInlineTagTree
|
||||
|
||||
try {
|
||||
return processInlineTag(tag)
|
||||
} catch (e: SnippetException) {
|
||||
reporter.print(
|
||||
Diagnostic.Kind.ERROR,
|
||||
DocTreePath.getPath(env.docTrees.getPath(element), env.docTrees.getDocCommentTree(element), tag),
|
||||
"Invalid @snippet. ${e.message}",
|
||||
)
|
||||
return "@snippet"
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
private val locations = EnumSet.allOf(Taglet.Location::class.java)
|
||||
private val attribute = Pattern.compile(" *([a-z]+) *= *([^ ]+)")
|
||||
|
||||
/** Escape our snippet HTML and wrap it into a code block */
|
||||
private fun makeSnippet(extension: String, contents: String): String {
|
||||
val out = StringBuilder(contents.length + 60)
|
||||
out.append("<pre class=\"language language-$extension\"><code>")
|
||||
for (element in contents) {
|
||||
when (element) {
|
||||
'<' -> out.append("<")
|
||||
'>' -> out.append(">")
|
||||
'&' -> out.append("&")
|
||||
else -> out.append(element)
|
||||
}
|
||||
}
|
||||
out.append("</code></pre>")
|
||||
return out.toString()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private class SnippetException(message: String) : Exception(message)
|
@ -8,25 +8,10 @@ pluginManagement {
|
||||
mavenCentral()
|
||||
gradlePluginPortal()
|
||||
|
||||
maven("https://maven.minecraftforge.net") {
|
||||
name = "Forge"
|
||||
maven("https://maven.neoforged.net") {
|
||||
name = "NeoForge"
|
||||
content {
|
||||
includeGroup("net.minecraftforge")
|
||||
includeGroup("net.minecraftforge.gradle")
|
||||
}
|
||||
}
|
||||
|
||||
maven("https://maven.parchmentmc.org") {
|
||||
name = "Librarian"
|
||||
content {
|
||||
includeGroupByRegex("^org\\.parchmentmc.*")
|
||||
}
|
||||
}
|
||||
|
||||
maven("https://repo.spongepowered.org/repository/maven-public/") {
|
||||
name = "Sponge"
|
||||
content {
|
||||
includeGroup("org.spongepowered")
|
||||
includeGroup("net.neoforged")
|
||||
}
|
||||
}
|
||||
|
||||
@ -45,14 +30,6 @@ pluginManagement {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
resolutionStrategy {
|
||||
eachPlugin {
|
||||
if (requested.id.id == "org.spongepowered.mixin") {
|
||||
useModule("org.spongepowered:mixingradle:${requested.version}")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
val mcVersion: String by settings
|
||||
|
Loading…
Reference in New Issue
Block a user