1
0
mirror of https://github.com/SquidDev-CC/CC-Tweaked synced 2025-10-23 09:57:39 +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:
Jonathan Coates
2025-01-09 20:47:51 +00:00
parent d9fc1c3a80
commit 3c46b8acd7
57 changed files with 1089 additions and 616 deletions

View File

@@ -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())

View File

@@ -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
}

View File

@@ -0,0 +1,20 @@
package com.example.examplemod;
import dan200.computercraft.api.client.turtle.RegisterTurtleModellersEvent;
import dan200.computercraft.api.client.turtle.TurtleUpgradeModeller;
import net.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
}

View File

@@ -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));
}
}

View File

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

View File

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