diff --git a/buildSrc/src/main/kotlin/cc-tweaked.java-convention.gradle.kts b/buildSrc/src/main/kotlin/cc-tweaked.java-convention.gradle.kts
index 4eddc8da1..e0bd006ec 100644
--- a/buildSrc/src/main/kotlin/cc-tweaked.java-convention.gradle.kts
+++ b/buildSrc/src/main/kotlin/cc-tweaked.java-convention.gradle.kts
@@ -54,6 +54,7 @@ repositories {
filter {
includeGroup("cc.tweaked")
// Things we mirror
+ includeGroup("com.simibubi.create")
includeGroup("commoble.morered")
includeGroup("dev.architectury")
includeGroup("dev.emi")
diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml
index 3ab4b08f0..1510581d8 100644
--- a/gradle/libs.versions.toml
+++ b/gradle/libs.versions.toml
@@ -46,6 +46,8 @@ oculus = "1.2.5"
rei = "12.0.626"
rubidium = "0.6.1"
sodium = "mc1.20-0.4.10"
+create-forge = "0.5.1.f-33"
+create-fabric = "0.5.1-f-build.1467+mc1.20.1"
# Testing
hamcrest = "2.2"
@@ -100,11 +102,13 @@ nightConfig-toml = { module = "com.electronwill.night-config:toml", version.ref
slf4j = { module = "org.slf4j:slf4j-api", version.ref = "slf4j" }
# Minecraft mods
-fabric-api = { module = "net.fabricmc.fabric-api:fabric-api", version.ref = "fabric-api" }
-fabric-loader = { module = "net.fabricmc:fabric-loader", version.ref = "fabric-loader" }
-fabric-junit = { module = "net.fabricmc:fabric-loader-junit", version.ref = "fabric-loader" }
-fabricPermissions = { module = "me.lucko:fabric-permissions-api", version.ref = "fabricPermissions" }
+create-fabric = { module = "com.simibubi.create:create-fabric-1.20.1", version.ref = "create-fabric" }
+create-forge = { module = "com.simibubi.create:create-1.20.1", version.ref = "create-forge" }
emi = { module = "dev.emi:emi-xplat-mojmap", version.ref = "emi" }
+fabric-api = { module = "net.fabricmc.fabric-api:fabric-api", version.ref = "fabric-api" }
+fabric-junit = { module = "net.fabricmc:fabric-loader-junit", version.ref = "fabric-loader" }
+fabric-loader = { module = "net.fabricmc:fabric-loader", version.ref = "fabric-loader" }
+fabricPermissions = { module = "me.lucko:fabric-permissions-api", version.ref = "fabricPermissions" }
iris = { module = "maven.modrinth:iris", version.ref = "iris" }
jei-api = { module = "mezz.jei:jei-1.20.1-common-api", version.ref = "jei" }
jei-fabric = { module = "mezz.jei:jei-1.20.1-fabric", version.ref = "jei" }
diff --git a/projects/common/build.gradle.kts b/projects/common/build.gradle.kts
index e0b20e3d0..200fb5ccb 100644
--- a/projects/common/build.gradle.kts
+++ b/projects/common/build.gradle.kts
@@ -43,6 +43,7 @@ dependencies {
clientImplementation(clientClasses(project(":common-api")))
compileOnly(libs.bundles.externalMods.common)
+ compileOnly(variantOf(libs.create.forge) { classifier("slim") }) { isTransitive = false }
clientCompileOnly(variantOf(libs.emi) { classifier("api") })
annotationProcessorEverywhere(libs.autoService)
diff --git a/projects/common/src/main/java/dan200/computercraft/shared/integration/CreateIntegration.java b/projects/common/src/main/java/dan200/computercraft/shared/integration/CreateIntegration.java
new file mode 100644
index 000000000..d7880d950
--- /dev/null
+++ b/projects/common/src/main/java/dan200/computercraft/shared/integration/CreateIntegration.java
@@ -0,0 +1,34 @@
+// SPDX-FileCopyrightText: 2024 The CC: Tweaked Developers
+//
+// SPDX-License-Identifier: MPL-2.0
+
+package dan200.computercraft.shared.integration;
+
+import com.simibubi.create.content.contraptions.BlockMovementChecks;
+import com.simibubi.create.content.contraptions.BlockMovementChecks.CheckResult;
+import dan200.computercraft.shared.peripheral.modem.wired.CableBlock;
+import dan200.computercraft.shared.peripheral.modem.wireless.WirelessModemBlock;
+
+/**
+ * Integration with Create.
+ */
+public final class CreateIntegration {
+ public static final String ID = "create";
+
+ private CreateIntegration() {
+ }
+
+ public static void setup() {
+ // Allow modems to be treated as "attached" to their adjacent block.
+ BlockMovementChecks.registerAttachedCheck((state, world, pos, direction) -> {
+ var block = state.getBlock();
+ if (block instanceof WirelessModemBlock) {
+ return CheckResult.of(state.getValue(WirelessModemBlock.FACING) == direction);
+ } else if (block instanceof CableBlock) {
+ return CheckResult.of(state.getValue(CableBlock.MODEM).getFacing() == direction);
+ } else {
+ return CheckResult.PASS;
+ }
+ });
+ }
+}
diff --git a/projects/common/src/main/java/dan200/computercraft/shared/integration/ExternalModTags.java b/projects/common/src/main/java/dan200/computercraft/shared/integration/ExternalModTags.java
index 29a666217..cf8aaba93 100644
--- a/projects/common/src/main/java/dan200/computercraft/shared/integration/ExternalModTags.java
+++ b/projects/common/src/main/java/dan200/computercraft/shared/integration/ExternalModTags.java
@@ -8,6 +8,7 @@ import net.minecraft.core.registries.Registries;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.tags.TagKey;
import net.minecraft.world.level.block.Block;
+import net.minecraft.world.level.block.state.BlockState;
/**
* Tags defined by external mods.
@@ -26,9 +27,9 @@ public final class ExternalModTags {
/**
* Create's "brittle" tag, used to determine if this block needs to be moved before its neighbours.
*
- * @see {@code BlockMovementChecks}
+ * @see com.simibubi.create.content.contraptions.BlockMovementChecks#isBrittle(BlockState)
*/
- public static final TagKey CREATE_BRITTLE = make("create", "brittle");
+ public static final TagKey CREATE_BRITTLE = make(CreateIntegration.ID, "brittle");
private static TagKey make(String mod, String name) {
return TagKey.create(Registries.BLOCK, new ResourceLocation(mod, name));
diff --git a/projects/fabric/build.gradle.kts b/projects/fabric/build.gradle.kts
index 19f98cfe9..69bc0d041 100644
--- a/projects/fabric/build.gradle.kts
+++ b/projects/fabric/build.gradle.kts
@@ -69,6 +69,7 @@ dependencies {
exclude("net.fabricmc", "fabric-loader")
exclude("net.fabricmc.fabric-api")
}
+ modCompileOnly(libs.create.fabric) { isTransitive = false }
modClientRuntimeOnly(libs.bundles.externalMods.fabric.runtime) {
exclude("net.fabricmc", "fabric-loader")
diff --git a/projects/fabric/src/main/java/dan200/computercraft/shared/ComputerCraft.java b/projects/fabric/src/main/java/dan200/computercraft/shared/ComputerCraft.java
index 1233608c0..b77d74388 100644
--- a/projects/fabric/src/main/java/dan200/computercraft/shared/ComputerCraft.java
+++ b/projects/fabric/src/main/java/dan200/computercraft/shared/ComputerCraft.java
@@ -13,6 +13,7 @@ import dan200.computercraft.shared.command.CommandComputerCraft;
import dan200.computercraft.shared.config.Config;
import dan200.computercraft.shared.config.ConfigSpec;
import dan200.computercraft.shared.details.FluidDetails;
+import dan200.computercraft.shared.integration.CreateIntegration;
import dan200.computercraft.shared.network.NetworkMessages;
import dan200.computercraft.shared.network.client.UpgradesLoadedMessage;
import dan200.computercraft.shared.network.server.ServerNetworking;
@@ -34,6 +35,7 @@ import net.fabricmc.fabric.api.loot.v2.LootTableEvents;
import net.fabricmc.fabric.api.networking.v1.ServerPlayNetworking;
import net.fabricmc.fabric.api.resource.IdentifiableResourceReloadListener;
import net.fabricmc.fabric.api.resource.ResourceManagerHelper;
+import net.fabricmc.loader.api.FabricLoader;
import net.minecraft.core.registries.BuiltInRegistries;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.server.packs.PackType;
@@ -119,6 +121,8 @@ public class ComputerCraft {
ComputerCraftAPI.registerGenericSource(new InventoryMethods());
Peripherals.addGenericLookup((world, pos, state, blockEntity, side, invalidate) -> InventoryMethods.extractContainer(world, pos, state, blockEntity, side));
+
+ if (FabricLoader.getInstance().isModLoaded(CreateIntegration.ID)) CreateIntegration.setup();
}
private record ReloadListener(String name, PreparableReloadListener listener)
diff --git a/projects/forge/build.gradle.kts b/projects/forge/build.gradle.kts
index 5c6108257..a1454b5df 100644
--- a/projects/forge/build.gradle.kts
+++ b/projects/forge/build.gradle.kts
@@ -121,6 +121,11 @@ dependencies {
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)) }
+
// Depend on our other projects.
api(commonClasses(project(":forge-api"))) { cct.exclude(this) }
clientApi(clientClasses(project(":forge-api"))) { cct.exclude(this) }
diff --git a/projects/forge/src/main/java/dan200/computercraft/ComputerCraft.java b/projects/forge/src/main/java/dan200/computercraft/ComputerCraft.java
index c198a94f2..9b50fe335 100644
--- a/projects/forge/src/main/java/dan200/computercraft/ComputerCraft.java
+++ b/projects/forge/src/main/java/dan200/computercraft/ComputerCraft.java
@@ -16,6 +16,7 @@ import dan200.computercraft.shared.CommonHooks;
import dan200.computercraft.shared.ModRegistry;
import dan200.computercraft.shared.config.ConfigSpec;
import dan200.computercraft.shared.details.FluidData;
+import dan200.computercraft.shared.integration.CreateIntegration;
import dan200.computercraft.shared.integration.MoreRedIntegration;
import dan200.computercraft.shared.peripheral.generic.methods.EnergyMethods;
import dan200.computercraft.shared.peripheral.generic.methods.FluidMethods;
@@ -78,6 +79,7 @@ public final class ComputerCraft {
ForgeDetailRegistries.FLUID_STACK.addProvider(FluidData::fill);
+ if (ModList.get().isLoaded(CreateIntegration.ID)) event.enqueueWork(CreateIntegration::setup);
if (ModList.get().isLoaded(MoreRedIntegration.MOD_ID)) MoreRedIntegration.setup();
}