1
0
mirror of https://github.com/SquidDev-CC/CC-Tweaked synced 2024-06-25 22:53:22 +00:00

Move our public API into separate modules

This adds two new modules: common-api and forge-api, which contain the
common and Forge-specific interfaces for CC's Minecraft-specific API.

We add a new PlatformHelper interface, which abstracts over some of the
loader-specific functionality, such as reading registries[^1] or calling
Forge-specific methods. This interface is then implemented in the main
mod, and loaded via ServiceLoaders.

Some other notes on this:

 - We now split shared and client-specific source code into separate
   modules. This is to make it harder to reference client code on the
   server, thus crashing the game.

   Eventually we'll split the main mod up too into separate source sets
   - this is, of course, a much bigger problem!

 - There's currently some nastiness here due to wanting to preserve
   binary compatibility of the API. We'll hopefully be able to remove
   this when 1.19.3 releases.

 - In order to build a separate Forge-specific API jar, we compile the
   common sources twice: once for the common jar and once for the Forge
   jar.

   Getting this to play nicely with IDEs is a little tricky and so we
   provide a cct.inlineProject(...) helper to handle everything.

[^1]: We /can/ do this with vanilla's APIs, but it gives a lot of
deprecation warnings. It just ends up being nicer to abstract over it.
This commit is contained in:
Jonathan Coates 2022-11-06 15:07:13 +00:00
parent d8e2161f15
commit 76710eec9d
No known key found for this signature in database
GPG Key ID: B9E431FF07C98D06
78 changed files with 956 additions and 96 deletions

View File

@ -6,9 +6,9 @@ import org.jetbrains.gradle.ext.settings
plugins {
// Build
alias(libs.plugins.forgeGradle)
id("net.minecraftforge.gradle")
alias(libs.plugins.mixinGradle)
alias(libs.plugins.librarian)
id("org.parchmentmc.librarian.forgegradle")
alias(libs.plugins.shadow)
// Publishing
alias(libs.plugins.curseForgeGradle)
@ -16,7 +16,7 @@ plugins {
alias(libs.plugins.minotaur)
// Utility
alias(libs.plugins.taskTree)
alias(libs.plugins.ideaExt)
id("org.jetbrains.gradle.plugin.idea-ext")
id("cc-tweaked.illuaminate")
id("cc-tweaked.gametest")
@ -28,17 +28,20 @@ val isStable = true
val modVersion: String by extra
val mcVersion: String by extra
val allProjects = listOf(":core-api", ":core").map { evaluationDependsOn(it) }
val allProjects = listOf(":core-api", ":core", ":forge-api").map { evaluationDependsOn(it) }
cct {
allProjects.forEach { externalSources(it) }
}
java {
withJavadocJar()
registerFeature("extraMods") { usingSourceSet(sourceSets.main.get()) }
}
sourceSets {
// ForgeGradle adds a dep on the clientClasses task, despite forge-api coming from a separate project. Register an
// empty one.
register("client")
main {
resources.srcDir("src/generated/resources")
}
@ -160,6 +163,8 @@ dependencies {
"extraModsCompileOnly"(fg.deobf("maven.modrinth:oculus:1.2.5"))
implementation(project(":core"))
implementation(commonClasses(project(":forge-api")))
implementation(clientClasses(project(":forge-api")))
"shade"(libs.cobalt)
"shade"(libs.netty.http)
@ -182,19 +187,6 @@ illuaminate {
// Compile tasks
tasks.javadoc {
include("dan200/computercraft/api/**/*.java")
}
val apiJar by tasks.registering(Jar::class) {
archiveClassifier.set("api")
from(sourceSets.main.get().output) {
include("dan200/computercraft/api/**/*")
}
}
tasks.assemble { dependsOn(apiJar) }
val luaJavadoc by tasks.registering(Javadoc::class) {
description = "Generates documentation for Java-side Lua functions."
group = JavaBasePlugin.DOCUMENTATION_GROUP
@ -344,7 +336,6 @@ tasks.publish { dependsOn(tasks.githubRelease) }
publishing {
publications {
named("maven", MavenPublication::class) {
artifact(apiJar)
fg.component(this)
}
}

View File

@ -8,10 +8,42 @@ repositories {
gradlePluginPortal()
}
// Duplicated in settings.gradle.kts
repositories {
mavenCentral()
gradlePluginPortal()
maven("https://maven.minecraftforge.net") {
name = "Forge"
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")
}
}
}
dependencies {
implementation(libs.errorProne.plugin)
implementation(libs.kotlin.plugin)
implementation(libs.spotless)
implementation(libs.vanillaGradle)
implementation(libs.forgeGradle)
implementation(libs.librarian)
}
gradlePlugin {

View File

@ -0,0 +1,35 @@
/** Default configuration for Forge projects. */
import cc.tweaked.gradle.CCTweakedExtension
import cc.tweaked.gradle.CCTweakedPlugin
import cc.tweaked.gradle.IdeaRunConfigurations
import cc.tweaked.gradle.MinecraftConfigurations
plugins {
id("cc-tweaked.java-convention")
id("net.minecraftforge.gradle")
id("org.parchmentmc.librarian.forgegradle")
}
plugins.apply(CCTweakedPlugin::class.java)
val mcVersion: String by extra
minecraft {
val libs = project.extensions.getByType<VersionCatalogsExtension>().named("libs")
mappings("parchment", "${libs.findVersion("parchmentMc").get()}-${libs.findVersion("parchment").get()}-$mcVersion")
accessTransformer(rootProject.file("src/main/resources/META-INF/accesstransformer.cfg"))
}
MinecraftConfigurations.setup(project)
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() }
}

View File

@ -0,0 +1,27 @@
/** Default configuration for non-modloader-specific Minecraft projects. */
import cc.tweaked.gradle.CCTweakedExtension
import cc.tweaked.gradle.CCTweakedPlugin
import cc.tweaked.gradle.MinecraftConfigurations
plugins {
id("cc-tweaked.java-convention")
id("org.spongepowered.gradle.vanilla")
}
plugins.apply(CCTweakedPlugin::class.java)
val mcVersion: String by extra
minecraft {
version(mcVersion)
}
dependencies {
val libs = project.extensions.getByType<VersionCatalogsExtension>().named("libs")
// Depend on error prone annotations to silence a lot of compile warnings.
compileOnlyApi(libs.findLibrary("errorProne.annotations").get())
}
MinecraftConfigurations.setup(project)

View File

@ -1,21 +1,31 @@
package cc.tweaked.gradle
import net.ltgt.gradle.errorprone.CheckSeverity
import net.ltgt.gradle.errorprone.errorprone
import org.gradle.api.NamedDomainObjectProvider
import org.gradle.api.Project
import org.gradle.api.attributes.TestSuiteType
import org.gradle.api.file.FileSystemOperations
import org.gradle.api.plugins.JavaPluginExtension
import org.gradle.api.provider.Provider
import org.gradle.api.provider.SetProperty
import org.gradle.api.reporting.ReportingExtension
import org.gradle.api.tasks.JavaExec
import org.gradle.api.tasks.SourceSet
import org.gradle.api.tasks.SourceSetContainer
import org.gradle.api.tasks.bundling.Jar
import org.gradle.api.tasks.compile.JavaCompile
import org.gradle.api.tasks.javadoc.Javadoc
import org.gradle.configurationcache.extensions.capitalized
import org.gradle.kotlin.dsl.get
import org.gradle.language.base.plugins.LifecycleBasePlugin
import org.gradle.language.jvm.tasks.ProcessResources
import org.gradle.testing.jacoco.plugins.JacocoCoverageReport
import org.gradle.testing.jacoco.plugins.JacocoPluginExtension
import org.gradle.testing.jacoco.plugins.JacocoTaskExtension
import org.gradle.testing.jacoco.tasks.JacocoReport
import org.jetbrains.kotlin.gradle.dsl.KotlinProjectExtension
import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
import java.io.BufferedWriter
import java.io.IOException
import java.io.OutputStreamWriter
@ -90,6 +100,61 @@ fun externalSources(project: Project) {
}
}
/**
* Add a dependency on another project such that its sources and compiles are processed with this one.
*
* This is used when importing a common library into a loader-specific one, as we want to compile sources using
* the loader-specific sources.
*/
fun inlineProject(path: String) {
val otherProject = project.evaluationDependsOn(path)
val otherJava = otherProject.extensions.getByType(JavaPluginExtension::class.java)
val main = otherJava.sourceSets.getByName("main")
val client = otherJava.sourceSets.getByName("client")
val testMod = otherJava.sourceSets.findByName("testMod")
val testFixtures = otherJava.sourceSets.findByName("testFixtures")
// Pull in sources from the other project.
extendSourceSet(otherProject, main)
extendSourceSet(otherProject, client)
if (testMod != null) extendSourceSet(otherProject, testMod)
if (testFixtures != null) extendSourceSet(otherProject, testFixtures)
// The extra source-processing tasks should include these files too.
project.tasks.named(main.javadocTaskName, Javadoc::class.java) { source(main.allJava, client.allJava) }
project.tasks.named(main.sourcesJarTaskName, Jar::class.java) { from(main.allSource, client.allSource) }
sourceDirectories.addAll(SourceSetReference.inline(main), SourceSetReference.inline(client))
}
/**
* Extend a source set with files from another project.
*
* This actually extends the original compile tasks, as extending the source sets does not play well with IDEs.
*/
private fun extendSourceSet(otherProject: Project, sourceSet: SourceSet) {
project.tasks.named(sourceSet.compileJavaTaskName, JavaCompile::class.java) {
dependsOn(otherProject.tasks.named(sourceSet.compileJavaTaskName)) // Avoid duplicate compile errors
source(sourceSet.allJava)
}
project.tasks.named(sourceSet.processResourcesTaskName, ProcessResources::class.java) {
from(sourceSet.resources)
}
// Also try to depend on Kotlin if it exists
val kotlin = otherProject.extensions.findByType(KotlinProjectExtension::class.java)
if (kotlin != null) {
val compileKotlin = sourceSet.getCompileTaskName("kotlin")
project.tasks.named(compileKotlin, KotlinCompile::class.java) {
dependsOn(otherProject.tasks.named(compileKotlin))
source(kotlin.sourceSets.getByName(sourceSet.name).kotlin)
}
}
// If we're doing an IDE sync, add a fake dependency to ensure it's on the classpath.
if (isIdeSync) project.dependencies.add(sourceSet.apiConfigurationName, sourceSet.output)
}
fun jacoco(task: NamedDomainObjectProvider<JavaExec>) {
val classDump = project.buildDir.resolve("jacocoClassDump/${task.name}")
val reportTaskName = "jacoco${task.name.capitalized()}Report"
@ -146,5 +211,8 @@ private fun <T> gitProvider(project: Project, default: T, supplier: () -> T): Pr
}
}
}
internal val isIdeSync: Boolean
get() = java.lang.Boolean.parseBoolean(System.getProperty("idea.sync.active", "false"))
}
}

View File

@ -18,3 +18,14 @@
spec.javaLauncher.set(javaLauncher)
spec.args = args
}
/**
* An alternative to [Nothing] with a more descriptive name. Use to enforce calling a function with named arguments:
*
* ```kotlin
* fun f(vararg unused: UseNamedArgs, arg1: Int, arg2: Int) {
* // ...
* }
* ```
*/
class UseNamedArgs private constructor()

View File

@ -0,0 +1,170 @@
package cc.tweaked.gradle
import org.gradle.api.Project
import org.gradle.api.logging.Logging
import org.w3c.dom.Attr
import org.w3c.dom.Document
import org.w3c.dom.Node
import org.xml.sax.InputSource
import java.nio.charset.StandardCharsets
import java.nio.file.Files
import java.nio.file.Path
import javax.xml.parsers.DocumentBuilderFactory
import javax.xml.transform.TransformerFactory
import javax.xml.transform.dom.DOMSource
import javax.xml.transform.stream.StreamResult
import javax.xml.xpath.XPathConstants
import javax.xml.xpath.XPathFactory
/**
* Patches up run configurations from ForgeGradle and Loom.
*
* Would be good to PR some (or all) of these changes upstream at some point.
*
* @see net.fabricmc.loom.configuration.ide.idea.IdeaSyncTask
* @see net.minecraftforge.gradle.common.util.runs.IntellijRunGenerator
*/
internal class IdeaRunConfigurations(project: Project) {
private val rootProject = project.rootProject
private val documentBuilder = DocumentBuilderFactory.newInstance().newDocumentBuilder()
private val xpath = XPathFactory.newInstance().newXPath()
private val writer = TransformerFactory.newInstance().newTransformer()
private val ideaDir = rootProject.file(".idea/")
private val buildDir: Lazy<String?> = lazy {
val ideaMisc = ideaDir.resolve("misc.xml")
try {
val doc = Files.newBufferedReader(ideaMisc.toPath(), StandardCharsets.UTF_8).use {
documentBuilder.parse(InputSource(it))
}
val node =
xpath.evaluate("//component[@name=\"ProjectRootManager\"]/output", doc, XPathConstants.NODE) as Node
val attr = node.attributes.getNamedItem("url") as Attr
attr.value.removePrefix("file://")
} catch (e: Exception) {
LOGGER.error("Failed to find root directory", e)
null
}
}
fun patch() = synchronized(LOCK) {
val runConfigDir = ideaDir.resolve("runConfigurations")
if (!runConfigDir.isDirectory) return
Files.list(runConfigDir.toPath()).use {
for (configuration in it) {
val filename = configuration.fileName.toString();
when {
filename.endsWith("_fabric.xml") -> patchFabric(configuration)
filename.startsWith("forge_") && filename.endsWith(".xml") -> patchForge(configuration)
else -> {}
}
}
}
}
private fun patchFabric(path: Path) = withXml(path) {
setXml("//configuration", "folderName") { "Fabric" }
}
private fun patchForge(path: Path) = withXml(path) {
val configId = path.fileName.toString().removePrefix("forge_").removeSuffix(".xml")
val sourceSet = forgeConfigs[configId]
if (sourceSet == null) {
LOGGER.error("[{}] Cannot map run configuration to a known source set", path)
return@withXml
}
setXml("//configuration", "folderName") { "Forge" }
setXml("//configuration/module", "name") { "${rootProject.name}.forge.$sourceSet" }
if (buildDir.value == null) return@withXml
setXml("//configuration/envs/env[@name=\"MOD_CLASSES\"]", "value") { classpath ->
val classes = classpath!!.split(':')
val newClasses = mutableListOf<String>()
fun appendUnique(x: String) {
if (!newClasses.contains(x)) newClasses.add(x)
}
for (entry in classes) {
if (!entry.contains("/out/")) {
appendUnique(entry)
continue
}
val match = CLASSPATH_ENTRY.matchEntire(entry)
if (match != null) {
val modId = match.groups["modId"]!!.value
val proj = match.groups["proj"]!!.value
var component = match.groups["component"]!!.value
if (component == "production") component = "main"
appendUnique(forgeModEntry(modId, proj, component))
} else {
LOGGER.warn("[{}] Unknown classpath entry {}", path, entry)
appendUnique(entry)
}
}
// Ensure common code is on the classpath
for (proj in listOf("common", "common-api")) {
for (component in listOf("main", "client")) {
appendUnique(forgeModEntry("computercraft", proj, component))
}
}
if (newClasses.any { it.startsWith("cctest%%") }) {
appendUnique(forgeModEntry("cctest", "core", "testFixtures"))
appendUnique(forgeModEntry("cctest", "common", "testMod"))
}
newClasses.joinToString(":")
}
}
private fun forgeModEntry(mod: String, project: String, component: String) =
"$mod%%${buildDir.value}/production/${rootProject.name}.$project.$component"
private fun LocatedDocument.setXml(xpath: String, attribute: String, value: (String?) -> String) {
val node = this@IdeaRunConfigurations.xpath.evaluate(xpath, document, XPathConstants.NODE) as Node?
if (node == null) {
LOGGER.error("[{}] Cannot find {}", path.fileName, xpath)
return
}
val attr = node.attributes.getNamedItem(attribute) as Attr? ?: document.createAttribute(attribute)
val oldValue = attr.value
attr.value = value(attr.value)
node.attributes.setNamedItem(attr)
if (oldValue != attr.value) {
LOGGER.info("[{}] Setting {}@{}:\n Old: {}\n New: {}", path.fileName, xpath, attribute, oldValue, attr.value)
}
}
private fun withXml(path: Path, run: LocatedDocument.() -> Unit) {
val doc = Files.newBufferedReader(path).use { documentBuilder.parse(InputSource(it)) }
run(LocatedDocument(path, doc))
Files.newBufferedWriter(path).use { writer.transform(DOMSource(doc), StreamResult(it)) }
}
private class LocatedDocument(val path: Path, val document: Document)
companion object {
private val LOGGER = Logging.getLogger(IdeaRunConfigurations::class.java)
private val LOCK = Any()
private val CLASSPATH_ENTRY =
Regex("(?<modId>[a-z]+)%%\\\$PROJECT_DIR\\\$/projects/(?<proj>[a-z-]+)/out/(?<component>\\w+)/(?<type>[a-z]+)\$")
private val forgeConfigs = mapOf(
"runClient" to "client",
"runData" to "main",
"runGameTestServer" to "testMod",
"runServer" to "main",
"runTestClient" to "testMod",
)
}
}

View File

@ -0,0 +1,189 @@
package cc.tweaked.gradle
import org.gradle.api.Project
import org.gradle.api.artifacts.Configuration
import org.gradle.api.artifacts.ModuleDependency
import org.gradle.api.artifacts.dsl.DependencyHandler
import org.gradle.api.attributes.Bundling
import org.gradle.api.attributes.Category
import org.gradle.api.attributes.LibraryElements
import org.gradle.api.attributes.Usage
import org.gradle.api.attributes.java.TargetJvmVersion
import org.gradle.api.capabilities.Capability
import org.gradle.api.plugins.BasePlugin
import org.gradle.api.plugins.JavaPluginExtension
import org.gradle.api.tasks.SourceSet
import org.gradle.api.tasks.bundling.Jar
import org.gradle.api.tasks.javadoc.Javadoc
import org.gradle.kotlin.dsl.get
import org.gradle.kotlin.dsl.named
/**
* This sets up a separate client-only source set, and extends that and the main/common source set with additional
* metadata, to make it easier to consume jars downstream.
*/
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]
/**
* Performs the initial setup of our configurations.
*/
private fun setup() {
// Define a client source set.
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])
}
// Set up an API configuration for clients (to ensure it's consistent with the main source set).
val clientApi = configurations.maybeCreate(client.apiConfigurationName).apply {
isVisible = false
isCanBeConsumed = false
isCanBeResolved = false
}
configurations.named(client.implementationConfigurationName) { extendsFrom(clientApi) }
/*
Now add outgoing variants for the main and common source sets that we can consume downstream. This is possibly
the worst way to do things, but unfortunately the alternatives don't actually work very well:
- Just using source set outputs: This means dependencies don't propagate, which means when :fabric depends
on :fabric-api, we don't inherit the fake :common-api in IDEA.
- Having separate common/main jars: Nice in principle, but unfortunately Forge needs a separate deobf jar
task (as the original jar is obfuscated), and IDEA is not able to map its output back to a source set.
This works for now, but is incredibly brittle. It's part of the reason we can't use testFixtures inside our
MC projects, as that adds a project(self) -> test dependency, which would pull in the jar instead.
Note we register a fake client jar here. It's not actually needed, but is there to make sure IDEA has
a way to tell that client classes are needed at runtime.
I'm so sorry, deeply aware how cursed this is.
*/
setupOutgoing(main, "CommonOnly")
project.tasks.register(client.jarTaskName, Jar::class.java) {
description = "An empty jar standing in for the client classes."
group = BasePlugin.BUILD_GROUP
archiveClassifier.set("client")
}
setupOutgoing(client)
// Reset the client classpath (Loom configures it slightly differently to this) and add a main -> client
// dependency. Here we /can/ use source set outputs as we add transitive deps by patching the classpath. Nasty,
// but avoids accidentally pulling in Forge's obfuscated jar.
client.compileClasspath = client.compileClasspath + main.compileClasspath
client.runtimeClasspath = client.runtimeClasspath + main.runtimeClasspath
project.dependencies.add(client.apiConfigurationName, main.output)
// Also add client classes to the test classpath. We do the same nasty tricks as needed for main -> client.
test.compileClasspath += client.compileClasspath
test.runtimeClasspath += client.runtimeClasspath
project.dependencies.add(test.implementationConfigurationName, client.output)
// Configure some tasks to include our additional files.
project.tasks.named("javadoc", Javadoc::class.java) {
source += client.allJava
classpath = main.compileClasspath + main.output + client.compileClasspath + client.output
}
// This are already done by Fabric, but we need it for Forge and vanilla. It shouldn't conflict at all.
project.tasks.named("jar", Jar::class.java) { from(client.output) }
project.tasks.named("sourcesJar", Jar::class.java) { from(client.allSource) }
project.extensions.configure(CCTweakedExtension::class.java) {
sourceDirectories.add(SourceSetReference.internal(client))
}
}
private fun setupOutgoing(sourceSet: SourceSet, suffix: String = "") {
setupOutgoing("${sourceSet.apiElementsConfigurationName}$suffix", sourceSet, objects.named(Usage.JAVA_API)) {
description = "API elements for ${sourceSet.name}"
extendsFrom(configurations[sourceSet.apiConfigurationName])
}
setupOutgoing("${sourceSet.runtimeElementsConfigurationName}$suffix", sourceSet, objects.named(Usage.JAVA_RUNTIME)) {
description = "Runtime elements for ${sourceSet.name}"
extendsFrom(configurations[sourceSet.implementationConfigurationName], configurations[sourceSet.runtimeOnlyConfigurationName])
}
}
/**
* Set up an outgoing configuration for a specific source set. We set an additional "main" or "client" capability
* (depending on the source set name) which allows downstream projects to consume them separately (see
* [DependencyHandler.commonClasses] and [DependencyHandler.clientClasses]).
*/
private fun setupOutgoing(name: String, sourceSet: SourceSet, usage: Usage, configure: Configuration.() -> Unit) {
configurations.register(name) {
isVisible = false
isCanBeConsumed = true
isCanBeResolved = false
configure(this)
attributes {
attribute(Category.CATEGORY_ATTRIBUTE, objects.named(Category.LIBRARY))
attribute(Usage.USAGE_ATTRIBUTE, usage)
attribute(Bundling.BUNDLING_ATTRIBUTE, objects.named(Bundling.EXTERNAL))
attributeProvider(
TargetJvmVersion.TARGET_JVM_VERSION_ATTRIBUTE,
java.toolchain.languageVersion.map { it.asInt() },
)
attribute(LibraryElements.LIBRARY_ELEMENTS_ATTRIBUTE, objects.named(LibraryElements.JAR))
}
outgoing {
capability(BasicOutgoingCapability(project, sourceSet.name))
// We have two outgoing variants here: the original jar and the classes.
artifact(project.tasks.named(sourceSet.jarTaskName))
variants.create("classes") {
attributes.attribute(LibraryElements.LIBRARY_ELEMENTS_ATTRIBUTE, objects.named(LibraryElements.CLASSES))
sourceSet.output.classesDirs.forEach { artifact(it) { builtBy(sourceSet.output) } }
}
}
}
}
companion object {
fun setup(project: Project) {
MinecraftConfigurations(project).setup()
}
}
}
private class BasicIncomingCapability(private val module: ModuleDependency, private val name: String) : Capability {
override fun getGroup(): String = module.group!!
override fun getName(): String = "${module.name}-$name"
override fun getVersion(): String? = null
}
private class BasicOutgoingCapability(private val project: Project, private val name: String) : Capability {
override fun getGroup(): String = project.group.toString()
override fun getName(): String = "${project.name}-$name"
override fun getVersion(): String = project.version.toString()
}
fun DependencyHandler.clientClasses(notation: Any): ModuleDependency {
val dep = create(notation) as ModuleDependency
dep.capabilities { requireCapability(BasicIncomingCapability(dep, "client")) }
return dep
}
fun DependencyHandler.commonClasses(notation: Any): ModuleDependency {
val dep = create(notation) as ModuleDependency
dep.capabilities { requireCapability(BasicIncomingCapability(dep, "main")) }
return dep
}

View File

@ -43,6 +43,7 @@ nullAway = "0.9.9"
shadow = "7.1.2"
spotless = "6.8.0"
taskTree = "2.1.0"
vanillaGradle = "0.2.1-SNAPSHOT"
[libraries]
asm = { module = "org.ow2.asm:asm", version.ref = "asm" }
@ -74,9 +75,12 @@ errorProne-api = { module = "com.google.errorprone:error_prone_check_api", versi
errorProne-core = { module = "com.google.errorprone:error_prone_core", version.ref = "errorProne-core" }
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" }
forgeGradle = { module = "net.minecraftforge.gradle:ForgeGradle", version.ref = "forgeGradle" }
kotlin-plugin = { module = "org.jetbrains.kotlin:kotlin-gradle-plugin", version.ref = "kotlin" }
librarian = { module = "org.parchmentmc:librarian", version.ref = "librarian" }
nullAway = { module = "com.uber.nullaway:nullaway", version.ref = "nullAway" }
spotless = { module = "com.diffplug.spotless:spotless-plugin-gradle", version.ref = "spotless" }
vanillaGradle = { module = "org.spongepowered:vanillagradle", version.ref = "vanillaGradle" }
[plugins]
curseForgeGradle = { id = "net.darkhax.curseforgegradle", version.ref = "curseForgeGradle" }

View File

@ -0,0 +1,18 @@
plugins {
id("cc-tweaked.java-convention")
id("cc-tweaked.publishing")
id("cc-tweaked.vanilla")
}
java {
withJavadocJar()
}
dependencies {
api(project(":core-api"))
compileOnly(project(":forge-stubs"))
}
tasks.javadoc {
include("dan200/computercraft/api/**/*.java")
}

View File

@ -9,7 +9,6 @@
import dan200.computercraft.api.turtle.ITurtleUpgrade;
import dan200.computercraft.api.turtle.TurtleUpgradeSerialiser;
import dan200.computercraft.impl.client.ComputerCraftAPIClientService;
import net.minecraftforge.fml.event.lifecycle.FMLClientSetupEvent;
import javax.annotation.Nonnull;
@ -20,7 +19,8 @@ private ComputerCraftAPIClient() {
/**
* Register a {@link TurtleUpgradeModeller} for a class of turtle upgrades.
* <p>
* This may be called at any point after registry creation, though it is recommended to call it within {@link FMLClientSetupEvent}.
* This may be called at any point after registry creation, though it is recommended to call it within your client
* setup step.
*
* @param serialiser The turtle upgrade serialiser.
* @param modeller The upgrade modeller.

View File

@ -6,6 +6,7 @@
package dan200.computercraft.api.client;
import com.mojang.math.Transformation;
import dan200.computercraft.impl.client.ClientPlatformHelper;
import net.minecraft.client.Minecraft;
import net.minecraft.client.resources.model.BakedModel;
import net.minecraft.client.resources.model.ModelResourceLocation;
@ -39,7 +40,7 @@ public static TransformedModel of(@Nonnull ModelResourceLocation location) {
public static TransformedModel of(@Nonnull ResourceLocation location) {
var modelManager = Minecraft.getInstance().getModelManager();
return new TransformedModel(modelManager.getModel(location));
return new TransformedModel(ClientPlatformHelper.get().getModel(modelManager, location));
}
public static TransformedModel of(@Nonnull ItemStack item, @Nonnull Transformation transform) {

View File

@ -11,17 +11,22 @@
import dan200.computercraft.api.turtle.ITurtleUpgrade;
import dan200.computercraft.api.turtle.TurtleSide;
import java.nio.FloatBuffer;
class TurtleUpgradeModellers {
private static final Transformation leftTransform = getMatrixFor(-0.40625f);
private static final Transformation rightTransform = getMatrixFor(0.40625f);
private static Transformation getMatrixFor(float offset) {
return new Transformation(new Matrix4f(new float[]{
var matrix = new Matrix4f();
matrix.load(FloatBuffer.wrap(new float[]{
0.0f, 0.0f, -1.0f, 1.0f + offset,
1.0f, 0.0f, 0.0f, 0.0f,
0.0f, -1.0f, 0.0f, 1.0f,
0.0f, 0.0f, 0.0f, 1.0f,
}));
matrix.transpose();
return new Transformation(matrix);
}
static final TurtleUpgradeModeller<ITurtleUpgrade> FLAT_ITEM = (upgrade, turtle, side) ->

View File

@ -0,0 +1,46 @@
/*
* This file is part of ComputerCraft - http://www.computercraft.info
* Copyright Daniel Ratcliffe, 2011-2022. Do not distribute without permission.
* Send enquiries to dratcliffe@gmail.com
*/
package dan200.computercraft.impl.client;
import dan200.computercraft.impl.Services;
import net.minecraft.client.resources.model.BakedModel;
import net.minecraft.client.resources.model.ModelManager;
import net.minecraft.client.resources.model.ModelResourceLocation;
import net.minecraft.resources.ResourceLocation;
import org.jetbrains.annotations.ApiStatus;
import javax.annotation.Nullable;
@ApiStatus.Internal
public interface ClientPlatformHelper {
/**
* Equivalent to {@link ModelManager#getModel(ModelResourceLocation)} but for arbitrary {@link ResourceLocation}s.
*
* @param manager The model manager.
* @param location The model location.
* @return The baked model.
*/
BakedModel getModel(ModelManager manager, ResourceLocation location);
static ClientPlatformHelper get() {
var instance = Instance.INSTANCE;
return instance == null ? Services.raise(ClientPlatformHelper.class, Instance.ERROR) : instance;
}
final class Instance {
static final @Nullable ClientPlatformHelper INSTANCE;
static final @Nullable Throwable ERROR;
static {
var helper = Services.tryLoad(ClientPlatformHelper.class);
INSTANCE = helper.instance();
ERROR = helper.error();
}
private Instance() {
}
}
}

View File

@ -30,7 +30,6 @@
import net.minecraft.world.level.Level;
import net.minecraftforge.common.capabilities.Capability;
import net.minecraftforge.common.util.LazyOptional;
import net.minecraftforge.fluids.FluidStack;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
@ -118,7 +117,7 @@ public static IMount createResourceMount(@Nonnull String domain, @Nonnull String
* @param provider The peripheral provider to register.
* @see IPeripheral
* @see IPeripheralProvider
* @deprecated Use {@link ForgeComputerCraftAPI#registerPeripheralProvider(IPeripheralProvider)} instead.
* @deprecated Use {@code dan200.computercraft.api.ForgeComputerCraftAPI#registerPeripheralProvider(IPeripheralProvider)} instead.
*/
@Deprecated(forRemoval = true)
public static void registerPeripheralProvider(@Nonnull IPeripheralProvider provider) {
@ -140,7 +139,7 @@ public static void registerGenericSource(@Nonnull GenericSource source) {
*
* @param capability The capability to register.
* @see GenericSource
* @deprecated Use {@link ForgeComputerCraftAPI} instead.
* @deprecated Use {@code dan200.computercraft.api.ForgeComputerCraftAPI} instead.
*/
@Deprecated(forRemoval = true)
public static void registerGenericCapability(@Nonnull Capability<?> capability) {
@ -199,7 +198,7 @@ public static void registerAPIFactory(@Nonnull ILuaAPIFactory factory) {
* such as {@code turtle.getItemDetail()} or {@code turtle.inspect()}.
*
* @param type The type of object that this provider can provide details for. Should be {@link BlockReference},
* {@link FluidStack} or {@link ItemStack}.
* {@code net.minecraftforge.fluids.FluidStack} or {@link ItemStack}.
* @param provider The detail provider to register.
* @param <T> The type of object that this provider can provide details for.
* @deprecated Use {@link DetailRegistry#addProvider(IDetailProvider)} to register your provider.
@ -229,7 +228,7 @@ public static IWiredNode createWiredNodeForElement(@Nonnull IWiredElement elemen
* @param side The side to extract the network element from
* @return The element's node
* @see IWiredElement#getNode()
* @deprecated Use {@link ForgeComputerCraftAPI#getWiredElementAt(BlockGetter, BlockPos, Direction)}
* @deprecated Use {@code dan200.computercraft.api.ForgeComputerCraftAPI#getWiredElementAt(BlockGetter, BlockPos, Direction)}
*/
@Nonnull
@Deprecated(forRemoval = true)

View File

@ -5,10 +5,8 @@
*/
package dan200.computercraft.api;
import dan200.computercraft.ComputerCraft;
import net.minecraft.core.Registry;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.tags.BlockTags;
import net.minecraft.tags.ItemTags;
import net.minecraft.tags.TagKey;
import net.minecraft.world.item.Item;
import net.minecraft.world.level.block.Block;
@ -24,7 +22,7 @@ public static class Items {
public static final TagKey<Item> MONITOR = make("monitor");
private static TagKey<Item> make(String name) {
return ItemTags.create(new ResourceLocation(ComputerCraft.MOD_ID, name));
return TagKey.create(Registry.ITEM_REGISTRY, new ResourceLocation(ComputerCraftAPI.MOD_ID, name));
}
}
@ -55,7 +53,7 @@ public static class Blocks {
public static final TagKey<Block> TURTLE_HOE_BREAKABLE = make("turtle_hoe_harvestable");
private static TagKey<Block> make(String name) {
return BlockTags.create(new ResourceLocation(ComputerCraft.MOD_ID, name));
return TagKey.create(Registry.BLOCK_REGISTRY, new ResourceLocation(ComputerCraftAPI.MOD_ID, name));
}
}
}

View File

@ -5,7 +5,6 @@
*/
package dan200.computercraft.api.peripheral;
import dan200.computercraft.api.ForgeComputerCraftAPI;
import net.minecraft.core.BlockPos;
import net.minecraft.core.Direction;
import net.minecraft.world.level.Level;
@ -19,11 +18,14 @@
* <p>
* If you have a {@link BlockEntity} which acts as a peripheral, you may alternatively expose the {@link IPeripheral}
* capability.
*
* @see dan200.computercraft.api.ComputerCraftAPI#registerPeripheralProvider(IPeripheralProvider)
* <p>
* {@code dan200.computercraft.api.ForgeComputerCraftAPI#registerPeripheralProvider(IPeripheralProvider)} should be used
* to register a peripheral provider.
*/
@FunctionalInterface
public interface IPeripheralProvider {
// TODO(1.19.3): Move to Forge and fix link above.
/**
* Produce an peripheral implementation from a block location.
*
@ -31,7 +33,6 @@ public interface IPeripheralProvider {
* @param pos The position the block is at.
* @param side The side to get the peripheral from.
* @return A peripheral, or {@link LazyOptional#empty()} if there is not a peripheral here you'd like to handle.
* @see ForgeComputerCraftAPI#registerPeripheralProvider(IPeripheralProvider)
*/
@Nonnull
LazyOptional<IPeripheral> getPeripheral(@Nonnull Level world, @Nonnull BlockPos pos, @Nonnull Direction side);

View File

@ -7,7 +7,6 @@
import dan200.computercraft.api.upgrades.UpgradeDataProvider;
import net.minecraft.data.DataGenerator;
import net.minecraftforge.data.event.GatherDataEvent;
import javax.annotation.Nonnull;
import java.util.function.Consumer;
@ -18,7 +17,6 @@
* This should be subclassed and registered to a {@link DataGenerator}. Override the {@link #addUpgrades(Consumer)} function,
* construct each upgrade, and pass them off to the provided consumer to generate them.
*
* @see GatherDataEvent To register your data provider
* @see PocketUpgradeSerialiser
*/
public abstract class PocketUpgradeDataProvider extends UpgradeDataProvider<IPocketUpgrade, PocketUpgradeSerialiser<?>> {

View File

@ -5,17 +5,16 @@
*/
package dan200.computercraft.api.pocket;
import dan200.computercraft.ComputerCraft;
import dan200.computercraft.api.ComputerCraftAPI;
import dan200.computercraft.api.upgrades.IUpgradeBase;
import dan200.computercraft.api.upgrades.UpgradeSerialiser;
import dan200.computercraft.internal.upgrades.SerialiserWithCraftingItem;
import dan200.computercraft.internal.upgrades.SimpleSerialiser;
import dan200.computercraft.impl.upgrades.SerialiserWithCraftingItem;
import dan200.computercraft.impl.upgrades.SimpleSerialiser;
import net.minecraft.core.Registry;
import net.minecraft.resources.ResourceKey;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.item.crafting.SimpleRecipeSerializer;
import net.minecraftforge.registries.DeferredRegister;
import net.minecraftforge.registries.IForgeRegistry;
import net.minecraftforge.registries.RegistryManager;
@ -36,11 +35,8 @@
public interface PocketUpgradeSerialiser<T extends IPocketUpgrade> extends UpgradeSerialiser<T> {
/**
* The ID for the associated registry.
* <p>
* This is largely intended for use with Forge Registry methods/classes, such as {@link DeferredRegister} and
* {@link RegistryManager#getRegistry(ResourceKey)}.
*/
ResourceKey<Registry<PocketUpgradeSerialiser<?>>> REGISTRY_ID = ResourceKey.createRegistryKey(new ResourceLocation(ComputerCraft.MOD_ID, "pocket_upgrade_serialiser"));
ResourceKey<Registry<PocketUpgradeSerialiser<?>>> REGISTRY_ID = ResourceKey.createRegistryKey(new ResourceLocation(ComputerCraftAPI.MOD_ID, "pocket_upgrade_serialiser"));
/**
* The associated registry.

View File

@ -8,8 +8,6 @@
import dan200.computercraft.api.peripheral.IPeripheral;
import dan200.computercraft.api.upgrades.IUpgradeBase;
import net.minecraft.core.Direction;
import net.minecraftforge.event.entity.player.AttackEntityEvent;
import net.minecraftforge.event.level.BlockEvent;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
@ -58,8 +56,8 @@ default IPeripheral createPeripheral(@Nonnull ITurtleAccess turtle, @Nonnull Tur
* Will only be called for Tool turtle. Called when turtle.dig() or turtle.attack() is called
* by the turtle, and the tool is required to do some work.
* <p>
* Conforming implementations should fire {@link BlockEvent.BreakEvent} for digging {@link AttackEntityEvent}
* for attacking.
* Conforming implementations should fire loader-specific events when using the tool, for instance Forge's
* {@code AttackEntityEvent}.
*
* @param turtle Access to the turtle that the tool resides on.
* @param side Which side of the turtle (left or right) the tool resides on.

View File

@ -8,14 +8,14 @@
import dan200.computercraft.api.ComputerCraftAPI;
import dan200.computercraft.api.ComputerCraftTags;
import dan200.computercraft.api.upgrades.UpgradeDataProvider;
import dan200.computercraft.impl.PlatformHelper;
import net.minecraft.core.Registry;
import net.minecraft.data.DataGenerator;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.tags.TagKey;
import net.minecraft.world.entity.ai.attributes.Attributes;
import net.minecraft.world.item.Item;
import net.minecraft.world.level.block.Block;
import net.minecraftforge.data.event.GatherDataEvent;
import net.minecraftforge.registries.ForgeRegistries;
import javax.annotation.Nonnull;
import java.util.function.Consumer;
@ -26,7 +26,6 @@
* This should be subclassed and registered to a {@link DataGenerator}. Override the {@link #addUpgrades(Consumer)} function,
* construct each upgrade, and pass them off to the provided consumer to generate them.
*
* @see GatherDataEvent To register your data provider
* @see TurtleUpgradeSerialiser
*/
public abstract class TurtleUpgradeDataProvider extends UpgradeDataProvider<ITurtleUpgrade, TurtleUpgradeSerialiser<?>> {
@ -128,10 +127,10 @@ public ToolBuilder breakable(@Nonnull TagKey<Block> breakable) {
*/
public void add(@Nonnull Consumer<Upgrade<TurtleUpgradeSerialiser<?>>> add) {
add.accept(new Upgrade<>(id, serialiser, s -> {
s.addProperty("item", ForgeRegistries.ITEMS.getKey(toolItem).toString());
s.addProperty("item", PlatformHelper.get().getRegistryKey(Registry.ITEM_REGISTRY, toolItem).toString());
if (adjective != null) s.addProperty("adjective", adjective);
if (craftingItem != null) {
s.addProperty("craftItem", ForgeRegistries.ITEMS.getKey(craftingItem).toString());
s.addProperty("craftItem", PlatformHelper.get().getRegistryKey(Registry.ITEM_REGISTRY, craftingItem).toString());
}
if (damageMultiplier != null) s.addProperty("damageMultiplier", damageMultiplier);
if (breakable != null) s.addProperty("breakable", breakable.location().toString());

View File

@ -5,20 +5,17 @@
*/
package dan200.computercraft.api.turtle;
import dan200.computercraft.ComputerCraft;
import dan200.computercraft.api.client.ComputerCraftAPIClient;
import dan200.computercraft.api.client.turtle.TurtleUpgradeModeller;
import dan200.computercraft.api.ComputerCraftAPI;
import dan200.computercraft.api.upgrades.IUpgradeBase;
import dan200.computercraft.api.upgrades.UpgradeSerialiser;
import dan200.computercraft.internal.upgrades.SerialiserWithCraftingItem;
import dan200.computercraft.internal.upgrades.SimpleSerialiser;
import dan200.computercraft.impl.upgrades.SerialiserWithCraftingItem;
import dan200.computercraft.impl.upgrades.SimpleSerialiser;
import net.minecraft.core.Registry;
import net.minecraft.resources.ResourceKey;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.item.crafting.RecipeSerializer;
import net.minecraft.world.item.crafting.SimpleRecipeSerializer;
import net.minecraftforge.registries.DeferredRegister;
import net.minecraftforge.registries.IForgeRegistry;
import net.minecraftforge.registries.RegistryManager;
@ -29,13 +26,12 @@
/**
* Reads a {@link ITurtleUpgrade} from disk and reads/writes it to a network packet.
* <p>
* These should be registered in a {@link IForgeRegistry} while the game is loading, much like {@link RecipeSerializer}s.
* It is suggested you use a {@link DeferredRegister}.
* These should be registered in a {@link Registry} while the game is loading, much like {@link RecipeSerializer}s.
* <p>
* 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</h2>
* <h2>Example (Forge)</h2>
* <pre>{@code
* static final DeferredRegister<TurtleUpgradeSerialiser<?>> SERIALISERS = DeferredRegister.create( TurtleUpgradeSerialiser.TYPE, "my_mod" );
*
@ -57,7 +53,7 @@
* }</pre>
* <p>
* Finally, we need to register a model for our upgrade. This is done with
* {@link ComputerCraftAPIClient#registerTurtleUpgradeModeller(TurtleUpgradeSerialiser, TurtleUpgradeModeller)}:
* {@link dan200.computercraft.api.client.ComputerCraftAPIClient#registerTurtleUpgradeModeller}:
*
* <pre>{@code
* // Register our model inside FMLClientSetupEvent
@ -69,16 +65,13 @@
* @param <T> The type of turtle upgrade this is responsible for serialising.
* @see ITurtleUpgrade
* @see TurtleUpgradeDataProvider
* @see TurtleUpgradeModeller
* @see dan200.computercraft.api.client.turtle.TurtleUpgradeModeller
*/
public interface TurtleUpgradeSerialiser<T extends ITurtleUpgrade> extends UpgradeSerialiser<T> {
/**
* The ID for the associated registry.
* <p>
* This is largely intended for use with Forge Registry methods/classes, such as {@link DeferredRegister} and
* {@link RegistryManager#getRegistry(ResourceKey)}.
*/
ResourceKey<Registry<TurtleUpgradeSerialiser<?>>> REGISTRY_ID = ResourceKey.createRegistryKey(new ResourceLocation(ComputerCraft.MOD_ID, "turtle_upgrade_serialiser"));
ResourceKey<Registry<TurtleUpgradeSerialiser<?>>> REGISTRY_ID = ResourceKey.createRegistryKey(new ResourceLocation(ComputerCraftAPI.MOD_ID, "turtle_upgrade_serialiser"));
/**
* The associated registry.

View File

@ -7,6 +7,7 @@
import dan200.computercraft.api.pocket.IPocketUpgrade;
import dan200.computercraft.api.turtle.ITurtleUpgrade;
import dan200.computercraft.impl.PlatformHelper;
import net.minecraft.Util;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.world.item.ItemStack;
@ -62,20 +63,20 @@ public interface IUpgradeBase {
* <p>
* The default check requires that any non-capability NBT is exactly the same as the
* crafting item, but this may be relaxed for your upgrade.
* <p>
* This is based on {@code net.minecraftforge.common.crafting.StrictNBTIngredient}'s check.
*
* @param stack The stack to check. This is guaranteed to be non-empty and have the same item as
* {@link #getCraftingItem()}.
* @return If this stack may be used to equip this upgrade.
* @see net.minecraftforge.common.crafting.StrictNBTIngredient#test(ItemStack) For the implementation of the default
* check.
*/
default boolean isItemSuitable(@Nonnull ItemStack stack) {
var crafting = getCraftingItem();
// A more expanded form of ItemStack.areShareTagsEqual, but allowing an empty tag to be equal to a
// null one.
var shareTag = stack.getItem().getShareTag(stack);
var craftingShareTag = crafting.getItem().getShareTag(crafting);
var shareTag = PlatformHelper.get().getShareTag(stack);
var craftingShareTag = PlatformHelper.get().getShareTag(crafting);
if (shareTag == craftingShareTag) return true;
if (shareTag == null) return craftingShareTag.isEmpty();
if (craftingShareTag == null) return shareTag.isEmpty();

View File

@ -8,8 +8,9 @@
import com.google.gson.JsonObject;
import com.google.gson.JsonParseException;
import dan200.computercraft.api.turtle.TurtleUpgradeSerialiser;
import dan200.computercraft.internal.upgrades.SerialiserWithCraftingItem;
import dan200.computercraft.internal.upgrades.SimpleSerialiser;
import dan200.computercraft.impl.PlatformHelper;
import dan200.computercraft.impl.upgrades.SerialiserWithCraftingItem;
import dan200.computercraft.impl.upgrades.SimpleSerialiser;
import net.minecraft.core.Registry;
import net.minecraft.data.CachedOutput;
import net.minecraft.data.DataGenerator;
@ -17,14 +18,15 @@
import net.minecraft.resources.ResourceKey;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.world.item.Item;
import net.minecraftforge.registries.ForgeRegistries;
import net.minecraftforge.registries.RegistryManager;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import javax.annotation.Nonnull;
import java.io.IOException;
import java.util.*;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.function.Consumer;
import java.util.function.Function;
@ -84,7 +86,7 @@ public final Upgrade<R> simpleWithCustomItem(@Nonnull ResourceLocation id, @Nonn
}
return new Upgrade<>(id, serialiser, s ->
s.addProperty("item", Objects.requireNonNull(ForgeRegistries.ITEMS.getKey(item), "Item is not registered").toString())
s.addProperty("item", PlatformHelper.get().getRegistryKey(Registry.ITEM_REGISTRY, item).toString())
);
}
@ -104,7 +106,6 @@ public final Upgrade<R> simpleWithCustomItem(@Nonnull ResourceLocation id, @Nonn
@Override
public final void run(@Nonnull CachedOutput cache) throws IOException {
var registry = RegistryManager.ACTIVE.getRegistry(this.registry);
var base = generator.getOutputFolder().resolve("data");
Set<ResourceLocation> seen = new HashSet<>();
@ -113,7 +114,7 @@ public final void run(@Nonnull CachedOutput cache) throws IOException {
if (!seen.add(upgrade.id())) throw new IllegalStateException("Duplicate upgrade " + upgrade.id());
var json = new JsonObject();
json.addProperty("type", Objects.requireNonNull(registry.getKey(upgrade.serialiser()), "Serialiser has not been registered").toString());
json.addProperty("type", PlatformHelper.get().getRegistryKey(registry, upgrade.serialiser()).toString());
upgrade.serialise().accept(json);
try {
@ -141,7 +142,7 @@ public final String getName() {
@Nonnull
public final R existingSerialiser(@Nonnull ResourceLocation id) {
var result = RegistryManager.ACTIVE.getRegistry(registry).getValue(id);
var result = PlatformHelper.get().getRegistryObject(registry, id);
if (result == null) throw new IllegalArgumentException("No such serialiser " + registry);
return result;
}

View File

@ -27,7 +27,6 @@
import net.minecraft.world.level.Level;
import net.minecraftforge.common.capabilities.Capability;
import net.minecraftforge.common.util.LazyOptional;
import net.minecraftforge.fluids.FluidStack;
import org.jetbrains.annotations.ApiStatus;
import javax.annotation.Nonnull;
@ -56,10 +55,12 @@ static ComputerCraftAPIService get() {
@Nullable
IMount createResourceMount(@Nonnull String domain, @Nonnull String subPath);
@Deprecated
void registerPeripheralProvider(@Nonnull IPeripheralProvider provider);
void registerGenericSource(@Nonnull GenericSource source);
@Deprecated
void registerGenericCapability(@Nonnull Capability<?> capability);
void registerBundledRedstoneProvider(@Nonnull IBundledRedstoneProvider provider);
@ -88,8 +89,6 @@ static ComputerCraftAPIService get() {
DetailRegistry<BlockReference> getBlockInWorldDetailRegistry();
DetailRegistry<FluidStack> getFluidStackDetailRegistry();
final class Instance {
static final @Nullable ComputerCraftAPIService INSTANCE;
static final @Nullable Throwable ERROR;

View File

@ -0,0 +1,82 @@
/*
* This file is part of ComputerCraft - http://www.computercraft.info
* Copyright Daniel Ratcliffe, 2011-2022. Do not distribute without permission.
* Send enquiries to dratcliffe@gmail.com
*/
package dan200.computercraft.impl;
import net.minecraft.core.Registry;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.resources.ResourceKey;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.world.item.ItemStack;
import org.jetbrains.annotations.ApiStatus;
import javax.annotation.Nullable;
/**
* Abstraction layer for Forge and Fabric. See implementations for more details.
* <p>
* Do <strong>NOT</strong> directly reference this class. It exists for internal use by the API.
*/
@ApiStatus.Internal
public interface PlatformHelper {
/**
* Get the current {@link PlatformHelper} instance.
*
* @return The current instance.
*/
static PlatformHelper get() {
var instance = Instance.INSTANCE;
return instance == null ? Services.raise(PlatformHelper.class, Instance.ERROR) : instance;
}
/**
* Get the unique ID for a registered object.
*
* @param registry The registry to look up this object in.
* @param object The object to look up.
* @param <T> The type of object the registry stores.
* @return The registered object's ID.
* @throws IllegalArgumentException If the registry or object are not registered.
*/
<T> ResourceLocation getRegistryKey(ResourceKey<Registry<T>> registry, T object);
/**
* Look up an ID in a registry, returning the registered object.
*
* @param registry The registry to look up this object in.
* @param id The ID to look up.
* @param <T> The type of object the registry stores.
* @return The resolved registry object.
* @throws IllegalArgumentException If the registry or object are not registered.
*/
<T> T getRegistryObject(ResourceKey<Registry<T>> registry, ResourceLocation id);
/**
* Get the subset of an {@link ItemStack}'s {@linkplain ItemStack#getTag() tag} which is synced to the client.
*
* @param item The stack.
* @return The item's tag.
*/
@Nullable
default CompoundTag getShareTag(ItemStack item) {
return item.getTag();
}
final class Instance {
static final @Nullable PlatformHelper INSTANCE;
static final @Nullable Throwable ERROR;
static {
// We don't want class initialisation to fail here (as that results in confusing errors). Instead, capture
// the error and rethrow it when accessing. This should be JITted away in the common case.
var helper = Services.tryLoad(PlatformHelper.class);
INSTANCE = helper.instance();
ERROR = helper.error();
}
private Instance() {
}
}
}

View File

@ -3,7 +3,7 @@
* Copyright Daniel Ratcliffe, 2011-2022. Do not distribute without permission.
* Send enquiries to dratcliffe@gmail.com
*/
package dan200.computercraft.internal.upgrades;
package dan200.computercraft.impl.upgrades;
import com.google.gson.JsonObject;
import dan200.computercraft.api.upgrades.IUpgradeBase;
@ -12,6 +12,7 @@
import net.minecraft.resources.ResourceLocation;
import net.minecraft.util.GsonHelper;
import net.minecraft.world.item.ItemStack;
import org.jetbrains.annotations.ApiStatus;
import javax.annotation.Nonnull;
import java.util.function.BiFunction;
@ -23,6 +24,7 @@
*
* @param <T> The upgrade that this class can serialise and deserialise.
*/
@ApiStatus.Internal
public abstract class SerialiserWithCraftingItem<T extends IUpgradeBase> implements UpgradeSerialiser<T> {
private final BiFunction<ResourceLocation, ItemStack, T> factory;

View File

@ -3,13 +3,14 @@
* Copyright Daniel Ratcliffe, 2011-2022. Do not distribute without permission.
* Send enquiries to dratcliffe@gmail.com
*/
package dan200.computercraft.internal.upgrades;
package dan200.computercraft.impl.upgrades;
import com.google.gson.JsonObject;
import dan200.computercraft.api.upgrades.IUpgradeBase;
import dan200.computercraft.api.upgrades.UpgradeSerialiser;
import net.minecraft.network.FriendlyByteBuf;
import net.minecraft.resources.ResourceLocation;
import org.jetbrains.annotations.ApiStatus;
import javax.annotation.Nonnull;
import java.util.function.Function;
@ -21,6 +22,7 @@
*
* @param <T> The upgrade that this class can serialise and deserialise.
*/
@ApiStatus.Internal
public abstract class SimpleSerialiser<T extends IUpgradeBase> implements UpgradeSerialiser<T> {
private final Function<ResourceLocation, T> constructor;

View File

@ -0,0 +1,18 @@
plugins {
id("cc-tweaked.forge")
id("cc-tweaked.publishing")
}
java {
withJavadocJar()
}
cct.inlineProject(":common-api")
dependencies {
api(project(":core-api"))
}
tasks.javadoc {
include("dan200/computercraft/api/**/*.java")
}

View File

@ -9,7 +9,7 @@
import dan200.computercraft.api.network.wired.IWiredElement;
import dan200.computercraft.api.peripheral.IPeripheral;
import dan200.computercraft.api.peripheral.IPeripheralProvider;
import dan200.computercraft.impl.ComputerCraftAPIService;
import dan200.computercraft.impl.ComputerCraftAPIForgeService;
import net.minecraft.core.BlockPos;
import net.minecraft.core.Direction;
import net.minecraft.world.level.BlockGetter;
@ -22,6 +22,8 @@
* The forge-specific entrypoint for ComputerCraft's API.
*/
public final class ForgeComputerCraftAPI {
// TODO(1.19.3): Rename me to ComputerCraftAPIForge
private ForgeComputerCraftAPI() {
}
@ -61,7 +63,7 @@ public static LazyOptional<IWiredElement> getWiredElementAt(@Nonnull BlockGetter
}
@Nonnull
private static ComputerCraftAPIService getInstance() {
return ComputerCraftAPIService.get();
private static ComputerCraftAPIForgeService getInstance() {
return ComputerCraftAPIForgeService.get();
}
}

View File

@ -5,7 +5,7 @@
*/
package dan200.computercraft.api.detail;
import dan200.computercraft.impl.ComputerCraftAPIService;
import dan200.computercraft.impl.ComputerCraftAPIForgeService;
import net.minecraftforge.fluids.FluidStack;
/**
@ -15,5 +15,5 @@ public class ForgeDetailRegistries {
/**
* Provides details for {@link FluidStack}.
*/
public static final DetailRegistry<FluidStack> FLUID_STACK = ComputerCraftAPIService.get().getFluidStackDetailRegistry();
public static final DetailRegistry<FluidStack> FLUID_STACK = ComputerCraftAPIForgeService.get().getFluidStackDetailRegistry();
}

View File

@ -0,0 +1,35 @@
/*
* This file is part of ComputerCraft - http://www.computercraft.info
* Copyright Daniel Ratcliffe, 2011-2022. Do not distribute without permission.
* Send enquiries to dratcliffe@gmail.com
*/
package dan200.computercraft.impl;
import dan200.computercraft.api.ForgeComputerCraftAPI;
import dan200.computercraft.api.detail.DetailRegistry;
import dan200.computercraft.api.peripheral.IPeripheralProvider;
import net.minecraftforge.common.capabilities.Capability;
import net.minecraftforge.fluids.FluidStack;
import org.jetbrains.annotations.ApiStatus;
/**
* A Forge-specific version of {@link ComputerCraftAPIService}.
* <p>
* Do <strong>NOT</strong> directly reference this class. It exists for internal use by the API.
*
* @see ForgeComputerCraftAPI
*/
@ApiStatus.Internal
public interface ComputerCraftAPIForgeService extends ComputerCraftAPIService {
static ComputerCraftAPIForgeService get() {
return (ComputerCraftAPIForgeService) ComputerCraftAPIService.get();
}
@Override
void registerPeripheralProvider(IPeripheralProvider provider);
@Override
void registerGenericCapability(Capability<?> capability);
DetailRegistry<FluidStack> getFluidStackDetailRegistry();
}

View File

@ -0,0 +1,8 @@
plugins {
id("cc-tweaked.java-convention")
}
// Skip checkstyle here, it's going to be deleted soon anyway!
tasks.checkstyleMain {
enabled = false
}

View File

@ -0,0 +1,12 @@
/*
* This file is part of ComputerCraft - http://www.computercraft.info
* Copyright Daniel Ratcliffe, 2011-2022. Do not distribute without permission.
* Send enquiries to dratcliffe@gmail.com
*/
package net.minecraftforge.common.capabilities;
@Deprecated
public class Capability<T> {
private Capability() {
}
}

View File

@ -0,0 +1,15 @@
/*
* This file is part of ComputerCraft - http://www.computercraft.info
* Copyright Daniel Ratcliffe, 2011-2022. Do not distribute without permission.
* Send enquiries to dratcliffe@gmail.com
*/
package net.minecraftforge.common.util;
public final class LazyOptional<T> {
private LazyOptional() {
}
public static <T> LazyOptional<T> empty() {
throw new UnsupportedOperationException();
}
}

View File

@ -0,0 +1,10 @@
/*
* This file is part of ComputerCraft - http://www.computercraft.info
* Copyright Daniel Ratcliffe, 2011-2022. Do not distribute without permission.
* Send enquiries to dratcliffe@gmail.com
*/
package net.minecraftforge.items;
@Deprecated
public interface IItemHandlerModifiable {
}

View File

@ -0,0 +1,10 @@
/*
* This file is part of ComputerCraft - http://www.computercraft.info
* Copyright Daniel Ratcliffe, 2011-2022. Do not distribute without permission.
* Send enquiries to dratcliffe@gmail.com
*/
package net.minecraftforge.registries;
@Deprecated
public interface IForgeRegistry<T> {
}

View File

@ -0,0 +1,18 @@
/*
* This file is part of ComputerCraft - http://www.computercraft.info
* Copyright Daniel Ratcliffe, 2011-2022. Do not distribute without permission.
* Send enquiries to dratcliffe@gmail.com
*/
package net.minecraftforge.registries;
@Deprecated
public class RegistryManager {
public static final RegistryManager ACTIVE = new RegistryManager();
private RegistryManager() {
}
public <T> IForgeRegistry<T> getRegistry(Object object) {
throw new UnsupportedOperationException();
}
}

View File

@ -16,9 +16,14 @@ pluginManagement {
val mcVersion: String by settings
rootProject.name = "cc-tweaked-$mcVersion"
include(":mc-stubs")
include(":core-api")
include(":core")
include(":mc-stubs")
include(":forge-stubs")
include(":common-api")
include(":forge-api")
include(":web")
for (project in rootProject.children) {

View File

@ -23,6 +23,7 @@
import dan200.computercraft.core.apis.ApiFactories;
import dan200.computercraft.core.asm.GenericMethod;
import dan200.computercraft.core.filesystem.FileMount;
import dan200.computercraft.impl.ComputerCraftAPIForgeService;
import dan200.computercraft.impl.ComputerCraftAPIService;
import dan200.computercraft.impl.TurtleRefuelHandlers;
import dan200.computercraft.impl.detail.DetailRegistryImpl;
@ -58,7 +59,7 @@
import static dan200.computercraft.shared.Capabilities.CAPABILITY_WIRED_ELEMENT;
@AutoService(ComputerCraftAPIService.class)
public final class ComputerCraftAPIImpl implements ComputerCraftAPIService {
public final class ComputerCraftAPIImpl implements ComputerCraftAPIForgeService {
private final DetailRegistry<ItemStack> itemStackDetails = new DetailRegistryImpl<>(ItemData::fillBasic);
private final DetailRegistry<BlockReference> blockDetails = new DetailRegistryImpl<>(BlockData::fillBasic);
private final DetailRegistry<FluidStack> fluidStackDetails = new DetailRegistryImpl<>(FluidData::fillBasic);

View File

@ -0,0 +1,20 @@
/*
* This file is part of ComputerCraft - http://www.computercraft.info
* Copyright Daniel Ratcliffe, 2011-2022. Do not distribute without permission.
* Send enquiries to dratcliffe@gmail.com
*/
package dan200.computercraft.client.platform;
import com.google.auto.service.AutoService;
import dan200.computercraft.impl.client.ClientPlatformHelper;
import net.minecraft.client.resources.model.BakedModel;
import net.minecraft.client.resources.model.ModelManager;
import net.minecraft.resources.ResourceLocation;
@AutoService(ClientPlatformHelper.class)
public class ClientPlatformHelperImpl implements ClientPlatformHelper {
@Override
public BakedModel getModel(ModelManager manager, ResourceLocation location) {
return manager.getModel(location);
}
}

View File

@ -0,0 +1,40 @@
/*
* This file is part of ComputerCraft - http://www.computercraft.info
* Copyright Daniel Ratcliffe, 2011-2022. Do not distribute without permission.
* Send enquiries to dratcliffe@gmail.com
*/
package dan200.computercraft.shared.platform;
import com.google.auto.service.AutoService;
import dan200.computercraft.impl.PlatformHelper;
import net.minecraft.core.Registry;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.resources.ResourceKey;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.world.item.ItemStack;
import net.minecraftforge.registries.RegistryManager;
import javax.annotation.Nullable;
@AutoService(PlatformHelper.class)
public class PlatformHelperImpl implements PlatformHelper {
@Override
public <T> ResourceLocation getRegistryKey(ResourceKey<Registry<T>> registry, T object) {
var key = RegistryManager.ACTIVE.getRegistry(registry).getKey(object);
if (key == null) throw new IllegalArgumentException(object + " was not registered in " + registry);
return key;
}
@Override
public <T> T getRegistryObject(ResourceKey<Registry<T>> registry, ResourceLocation id) {
var value = RegistryManager.ACTIVE.getRegistry(registry).getValue(id);
if (value == null) throw new IllegalArgumentException(id + " was not registered in " + registry);
return value;
}
@Nullable
@Override
public CompoundTag getShareTag(ItemStack item) {
return item.getShareTag();
}
}