1
0
mirror of https://github.com/SquidDev-CC/CC-Tweaked synced 2025-11-01 22:22:59 +00:00

Compare commits

..

12 Commits

Author SHA1 Message Date
Jonathan Coates
8be6b1b772 Bump CC:T to 1.109.3 2024-01-03 20:00:52 +00:00
Jonathan Coates
104d5e70de Update to Cobalt 0.8.2
- Fix error when using "goto" as the first statement in an if block.
 - Fix incorrect resizing of table's hash part when adding and removing
   keys.
2024-01-03 18:44:37 +00:00
Jonathan Coates
e3bda2f763 Add command computers to the operator blocks tab
Fixes #1666
2024-01-03 18:42:31 +00:00
Jonathan Coates
234f69e8e5 Add a MessageType for network messages
Everything old is new again!

CC's network message implementation has gone through several iterations:

 - Originally network messages were implemented with a single class,
   which held an packet id/type and and opaque blobs of data (as
   string/int/byte/NBT arrays), and a big switch statement to decode and
   process this data.

 - In 42d3901ee3, we split the messages
   into different classes all inheriting from NetworkMessage - this bit
   we've stuck with ever since.

   Each packet had a `getId(): int` method, which returned the
   discriminator for this packet.

 - However, getId() was only used when registering the packet, not when
   sending, and so in ce0685c31f we
   removed it, just passing in a constant integer at registration
   instead.

 - In 53abe5e56e, we made some relatively
   minor changes to make the code more multi-loader/split-source
   friendly. However, this meant when we finally came to add Fabric
   support (8152f19b6e), we had to
   re-implement a lot of Forge's network code.

In 1.20.4, Forge moves to a system much closer to Fabric's (and indeed,
Minecraft's own CustomPacketPayload), and so it makes sense to adapt to
that now. As such, we:

 - Add a new MessageType interface. This is implemented by the
   loader-specific modules, and holds whatever information is needed to
   register the packet (e.g. discriminator, reader function).

 - Each NetworkMessage now has a type(): MessageType<?> function. This
   is used by the Fabric networking code (and for NeoForge's on 1.20.4)
   instead of a class lookup.

 - NetworkMessages now creates/stores these MessageType<T>s (much like
   we'd do for registries), and provides getters for the
   clientbound/serverbound messages. Mod initialisers then call these
   getters to register packets.

 - For Forge, this is relatively unchanged. For Fabric, we now
   `FabricPacket`s.
2024-01-03 10:23:41 +00:00
Jonathan Coates
ed3a17f9b9 Fix trailing-comma errors on method calls
We were only matching `f(a, ` patterns, and not `x:f(a, `. We now just
match against any usages of call_args - hadn't quite realised we could
do that!
2023-12-28 17:07:39 +00:00
Jonathan Coates
0349c2b1f9 Allow local domains in the standalone emulator
- Clean up option parsing a bit, so it uses the Option, rather than its
   corresponding character code.
 - Add a new -L/--allow-local-domains flag to remove the $private rule
   from the HTTP rules.
2023-12-20 17:43:08 +00:00
Jonathan Coates
03f9e6bd6d Prevent sending too many websocket messages at once 2023-12-20 17:30:57 +00:00
Jonathan Coates
9d8c933a14 Remove several usages of ComputerFamily
While ComputerFamily is still useful, there's definitely some places
where it adds an extra layer of indirection. This commit attempts to
clean up some places where we no longer need it.

 - Remove ComputerFamily from AbstractComputerBlock. The only place this
   was needed is in TurtleBlock, and that can be replaced with normal
   Minecraft explosion resistence!

 - Pass in the fuel limit to the turtle block entity, rather than
   deriving it from current family.

 - The turtle BERs now derive their model from the turtle's item, rather
   than the turtle's family.

 - When creating upgrade/overlay recipes, use the item's name, rather
   than {pocket,turtle}_family. This means we can drop getFamily() from
   IComputerItem (it is still needed on to handle the UI).

 - We replace IComputerItem.withFamily with a method to change to a
   different item of the same type. ComputerUpgradeRecipe no longer
   takes a family, and instead just uses the result's item.

 - Computer blocks now use the normal Block.asItem() to find their
   corresponding item, rather than looking it up via family.

The above means we can remove all the family-based XyzItem.create(...)
methods, which have always felt a little ugly.

We still need ComputerFamily for a couple of things:
 - Permission checks for command computers.
 - Checks for mouse/colour support in ServerComputer.
 - UI textures.
2023-12-20 14:17:38 +00:00
Jonathan Coates
78bb3da58c Improve our version tooling
- Add a check to ensure declared dependencies in the :core project, and
   those inherited from Minecraft are the same.
 - Compute the next Cobalt version, rather than specifying it manually.
 - Add the gradle versions plugin (and version catalog update), and
   update some versions.
2023-12-19 18:12:21 +00:00
Jonathan Coates
39a5e40c92 Bump CC:T to 1.109.2
Take two!
2023-12-16 22:46:30 +00:00
Jonathan Coates
763ba51919 Update Cobalt to 0.8.1 2023-12-16 22:39:48 +00:00
Jonathan Coates
cf6ec8c28f Add a slightly cleaner system for excluding deps
Previously we prevented our published full jar depending on any of the
other projects by excluding the whole cc.tweaked jar. However, as Cobalt
also now lives in that group, this meant we were missing the Cobalt
dependency.

Rather than specifying a wildcard, we now exclude the dependencies when
adding them to the project.
2023-12-16 22:35:15 +00:00
92 changed files with 908 additions and 516 deletions

View File

@@ -13,6 +13,8 @@ plugins {
publishing
alias(libs.plugins.taskTree)
alias(libs.plugins.githubRelease)
alias(libs.plugins.gradleVersions)
alias(libs.plugins.versionCatalogUpdate)
id("org.jetbrains.gradle.plugin.idea-ext")
id("cc-tweaked")
}
@@ -102,3 +104,9 @@ idea.project.settings.compiler.javac {
}
.toMap()
}
versionCatalogUpdate {
sortByKey.set(false)
pin { versions.addAll("fastutil", "guava", "netty", "slf4j") }
keep { keepUnusedLibraries.set(true) }
}

View File

@@ -5,6 +5,8 @@
plugins {
`java-gradle-plugin`
`kotlin-dsl`
alias(libs.plugins.gradleVersions)
alias(libs.plugins.versionCatalogUpdate)
}
// Duplicated in settings.gradle.kts
@@ -75,3 +77,9 @@ gradlePlugin {
}
}
}
versionCatalogUpdate {
sortByKey.set(false)
keep { keepUnusedLibraries.set(true) }
catalogFile.set(file("../gradle/libs.versions.toml"))
}

View File

@@ -76,6 +76,12 @@ dependencies {
val libs = project.extensions.getByType<VersionCatalogsExtension>().named("libs")
checkstyle(libs.findLibrary("checkstyle").get())
constraints {
checkstyle("org.codehaus.plexus:plexus-container-default:2.1.1") {
because("2.1.0 depends on deprecated Google collections module")
}
}
errorprone(libs.findLibrary("errorProne-core").get())
errorprone(libs.findLibrary("nullAway").get())
}
@@ -201,6 +207,7 @@ spotless {
val ktlintConfig = mapOf(
"ktlint_standard_no-wildcard-imports" to "disabled",
"ktlint_standard_class-naming" to "disabled",
"ktlint_standard_function-naming" to "disabled",
"ij_kotlin_allow_trailing_comma" to "true",
"ij_kotlin_allow_trailing_comma_on_call_site" to "true",
)

View File

@@ -10,9 +10,11 @@ import org.gradle.api.GradleException
import org.gradle.api.NamedDomainObjectProvider
import org.gradle.api.Project
import org.gradle.api.Task
import org.gradle.api.artifacts.Dependency
import org.gradle.api.attributes.TestSuiteType
import org.gradle.api.file.FileSystemOperations
import org.gradle.api.plugins.JavaPluginExtension
import org.gradle.api.provider.ListProperty
import org.gradle.api.provider.Provider
import org.gradle.api.provider.SetProperty
import org.gradle.api.reporting.ReportingExtension
@@ -73,11 +75,17 @@ abstract class CCTweakedExtension(
*/
val sourceDirectories: SetProperty<SourceSetReference> = project.objects.setProperty(SourceSetReference::class.java)
/**
* Dependencies excluded from published artifacts.
*/
private val excludedDeps: ListProperty<Dependency> = project.objects.listProperty(Dependency::class.java)
/** All source sets referenced by this project. */
val sourceSets = sourceDirectories.map { x -> x.map { it.sourceSet } }
init {
sourceDirectories.finalizeValueOnRead()
excludedDeps.finalizeValueOnRead()
project.afterEvaluate { sourceDirectories.disallowChanges() }
}
@@ -246,6 +254,20 @@ abstract class CCTweakedExtension(
).resolve().single()
}
/**
* Exclude a dependency from being publisehd in Maven.
*/
fun exclude(dep: Dependency) {
excludedDeps.add(dep)
}
/**
* Configure a [MavenDependencySpec].
*/
fun configureExcludes(spec: MavenDependencySpec) {
for (dep in excludedDeps.get()) spec.exclude(dep)
}
companion object {
private val COMMIT_COUNTS = Pattern.compile("""^\s*[0-9]+\s+(.*)$""")
private val IGNORED_USERS = setOf(

View File

@@ -0,0 +1,72 @@
// SPDX-FileCopyrightText: 2023 The CC: Tweaked Developers
//
// SPDX-License-Identifier: MPL-2.0
package cc.tweaked.gradle
import org.gradle.api.DefaultTask
import org.gradle.api.GradleException
import org.gradle.api.artifacts.Configuration
import org.gradle.api.artifacts.component.ModuleComponentIdentifier
import org.gradle.api.artifacts.component.ModuleComponentSelector
import org.gradle.api.artifacts.component.ProjectComponentIdentifier
import org.gradle.api.artifacts.result.DependencyResult
import org.gradle.api.artifacts.result.ResolvedDependencyResult
import org.gradle.api.provider.ListProperty
import org.gradle.api.tasks.Input
import org.gradle.api.tasks.TaskAction
import org.gradle.language.base.plugins.LifecycleBasePlugin
abstract class DependencyCheck : DefaultTask() {
@get:Input
abstract val configuration: ListProperty<Configuration>
init {
description = "Check :core's dependencies are consistent with Minecraft's."
group = LifecycleBasePlugin.VERIFICATION_GROUP
}
@TaskAction
fun run() {
var ok = true
for (configuration in configuration.get()) {
configuration.incoming.resolutionResult.allDependencies {
if (!check(this@allDependencies)) ok = false
}
}
if (!ok) {
throw GradleException("Mismatched versions in Minecraft dependencies. gradle/libs.versions.toml may need updating.")
}
}
private fun check(dependency: DependencyResult): Boolean {
if (dependency !is ResolvedDependencyResult) {
logger.warn("Found unexpected dependency result {}", dependency)
return false
}
// Skip dependencies on non-modules.
val requested = dependency.requested
if (requested !is ModuleComponentSelector) return true
// If this dependency is specified within some project (so is non-transitive), or is pulled in via Minecraft,
// then check for consistency.
// It would be nice to be smarter about transitive dependencies, but avoiding false positives is hard.
val from = dependency.from.id
if (
from is ProjectComponentIdentifier ||
from is ModuleComponentIdentifier && (from.group == "net.minecraft" || from.group == "io.netty")
) {
// If the version is different between the requested and selected version, report an error.
val selected = dependency.selected.moduleVersion!!.version
if (requested.version != selected) {
logger.error("Requested dependency {} (via {}) but got version {}", requested, from, selected)
return false
}
return true
}
return true
}
}

View File

@@ -6,7 +6,6 @@ package cc.tweaked.gradle
import org.gradle.api.artifacts.dsl.DependencyHandler
import org.gradle.api.file.FileSystemLocation
import org.gradle.api.file.FileSystemLocationProperty
import org.gradle.api.provider.Property
import org.gradle.api.provider.Provider
import org.gradle.api.tasks.JavaExec
@@ -129,3 +128,30 @@ fun <T> Property<T>.setProvider(provider: Provider<out T>) = set(provider)
/** Short-cut method to get the absolute path of a [FileSystemLocation] provider. */
fun Provider<out FileSystemLocation>.getAbsolutePath(): String = get().asFile.absolutePath
/**
* Get the version immediately after the provided version.
*
* For example, given "1.2.3", this will return "1.2.4".
*/
fun getNextVersion(version: String): String {
// Split a version like x.y.z-SNAPSHOT into x.y.z and -SNAPSHOT
val dashIndex = version.indexOf('-')
val mainVersion = if (dashIndex < 0) version else version.substring(0, dashIndex)
// Find the last component in x.y.z and increment it.
val lastIndex = mainVersion.lastIndexOf('.')
if (lastIndex < 0) throw IllegalArgumentException("Cannot parse version format \"$version\"")
val lastVersion = try {
version.substring(lastIndex + 1).toInt()
} catch (e: NumberFormatException) {
throw IllegalArgumentException("Cannot parse version format \"$version\"", e)
}
// Then append all components together.
val out = StringBuilder()
out.append(version, 0, lastIndex + 1)
out.append(lastVersion + 1)
if (dashIndex >= 0) out.append(version, dashIndex, version.length)
return out.toString()
}

View File

@@ -6,6 +6,8 @@ package cc.tweaked.gradle
import org.gradle.api.artifacts.Dependency
import org.gradle.api.artifacts.MinimalExternalModuleDependency
import org.gradle.api.artifacts.ProjectDependency
import org.gradle.api.plugins.BasePluginExtension
import org.gradle.api.publish.maven.MavenPublication
import org.gradle.api.specs.Spec
@@ -26,8 +28,13 @@ class MavenDependencySpec {
fun exclude(dep: Dependency) {
exclude {
// We have to cheat a little for project dependencies, as the project name doesn't match the artifact group.
val name = when (dep) {
is ProjectDependency -> dep.dependencyProject.extensions.getByType(BasePluginExtension::class.java).archivesName.get()
else -> dep.name
}
(dep.group.isNullOrEmpty() || dep.group == it.groupId) &&
(dep.name.isNullOrEmpty() || dep.name == it.artifactId) &&
(name.isNullOrEmpty() || name == it.artifactId) &&
(dep.version.isNullOrEmpty() || dep.version == it.version)
}
}

View File

@@ -109,6 +109,15 @@ class MinecraftConfigurations private constructor(private val project: Project)
project.extensions.configure(CCTweakedExtension::class.java) {
sourceDirectories.add(SourceSetReference.internal(client))
}
// Register a task to check there are no conflicts with the core project.
val checkDependencyConsistency =
project.tasks.register("checkDependencyConsistency", DependencyCheck::class.java) {
// We need to check both the main and client classpath *configurations*, as the actual configuration
configuration.add(configurations.named(main.runtimeClasspathConfigurationName))
configuration.add(configurations.named(client.runtimeClasspathConfigurationName))
}
project.tasks.named("check") { dependsOn(checkDependencyConsistency) }
}
private fun setupOutgoing(sourceSet: SourceSet, suffix: String = "") {

View File

@@ -21,7 +21,7 @@ of the mod should run fine on later versions.
However, some changes to the underlying game, or CC: Tweaked's own internals may break some programs. This page serves
as documentation for breaking changes and "gotchas" one should look out for between versions.
## CC: Tweaked 1.109.0 {#cct-1.109}
## CC: Tweaked 1.109.0 to 1.109.2 {#cct-1.109}
- Update to Lua 5.2:
- Support for Lua 5.0's pseudo-argument `arg` has been removed. You should always use `...` for varargs.
@@ -31,6 +31,7 @@ as documentation for breaking changes and "gotchas" one should look out for betw
- `load`/`loadstring` defaults to using the global environment (`_G`) rather than the current coroutine's
environment.
- Support for dumping functions (`string.dump`) and loading binary chunks has been removed.
- `math.random` now uses Lua 5.4's random number generator.
- File handles, HTTP requests and websockets now always use the original bytes rather than encoding/decoding to UTF-8.

View File

@@ -9,8 +9,8 @@ kotlin.stdlib.default.dependency=false
kotlin.jvm.target.validation.mode=error
# Mod properties
isUnstable=true
modVersion=1.109.1
isUnstable=false
modVersion=1.109.3
# Minecraft properties: We want to configure this here so we can read it in settings.gradle
mcVersion=1.20.1

View File

@@ -10,28 +10,29 @@
fabric-api = "0.86.1+1.20.1"
fabric-loader = "0.14.21"
forge = "47.1.0"
forgeSpi = "6.0.0"
forgeSpi = "7.0.1"
mixin = "0.8.5"
parchment = "2023.08.20"
parchmentMc = "1.20.1"
# Normal dependencies
asm = "9.5"
autoService = "1.1.1"
checkerFramework = "3.32.0"
cobalt = "0.8.0"
cobalt-next = "0.8.1" # Not a real version, used to constrain the version we accept.
commonsCli = "1.3.1"
# Core dependencies (these versions are tied to the version Minecraft uses)
fastutil = "8.5.9"
guava = "31.1-jre"
jetbrainsAnnotations = "24.0.1"
netty = "4.1.82.Final"
slf4j = "2.0.1"
# Core dependencies (independent of Minecraft)
asm = "9.6"
autoService = "1.1.1"
checkerFramework = "3.42.0"
cobalt = "0.8.2"
commonsCli = "1.6.0"
jetbrainsAnnotations = "24.1.0"
jsr305 = "3.0.2"
jzlib = "1.1.3"
kotlin = "1.8.10"
kotlin-coroutines = "1.6.4"
netty = "4.1.82.Final"
kotlin = "1.9.21"
kotlin-coroutines = "1.7.3"
nightConfig = "3.6.7"
slf4j = "2.0.1"
# Minecraft mods
emi = "1.0.8+1.20.1"
@@ -47,29 +48,31 @@ sodium = "mc1.20-0.4.10"
# Testing
hamcrest = "2.2"
jqwik = "1.7.4"
junit = "5.10.0"
jqwik = "1.8.2"
junit = "5.10.1"
# Build tools
cctJavadoc = "1.8.2"
checkstyle = "10.12.3"
checkstyle = "10.12.6"
curseForgeGradle = "1.0.14"
errorProne-core = "2.21.1"
errorProne-core = "2.23.0"
errorProne-plugin = "3.1.0"
fabric-loom = "1.3.7"
fabric-loom = "1.3.9"
forgeGradle = "6.0.8"
githubRelease = "2.4.1"
githubRelease = "2.5.2"
gradleVersions = "0.50.0"
ideaExt = "1.1.7"
illuaminate = "0.1.0-44-g9ee0055"
librarian = "1.+"
lwjgl = "3.3.1"
lwjgl = "3.3.3"
minotaur = "2.+"
mixinGradle = "0.7.+"
mixinGradle = "0.7.38"
nullAway = "0.9.9"
spotless = "6.21.0"
spotless = "6.23.3"
taskTree = "2.1.1"
teavm = "0.10.0-SQUID.2"
vanillaGradle = "0.2.1-SNAPSHOT"
versionCatalogUpdate = "0.8.1"
vineflower = "1.11.0"
[libraries]
@@ -162,10 +165,12 @@ vineflower = { module = "io.github.juuxel:loom-vineflower", version.ref = "vinef
[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" }
mixinGradle = { id = "org.spongepowered.mixin", version.ref = "mixinGradle" }
taskTree = { id = "com.dorongold.task-tree", version.ref = "taskTree" }
versionCatalogUpdate = { id = "nl.littlerobots.version-catalog-update", version.ref = "versionCatalogUpdate" }
[bundles]
annotations = ["jsr305", "checkerFramework", "jetbrainsAnnotations"]
@@ -184,5 +189,5 @@ test = ["junit-jupiter-api", "junit-jupiter-params", "hamcrest", "jqwik-api"]
testRuntime = ["junit-jupiter-engine", "jqwik-engine"]
# Build tools
teavm-api = [ "teavm-jso", "teavm-jso-apis", "teavm-platform", "teavm-classlib", "teavm-metaprogramming-api" ]
teavm-tooling = [ "teavm-tooling", "teavm-metaprogramming-impl", "teavm-jso-impl" ]
teavm-api = ["teavm-jso", "teavm-jso-apis", "teavm-platform", "teavm-classlib", "teavm-metaprogramming-api"]
teavm-tooling = ["teavm-tooling", "teavm-metaprogramming-impl", "teavm-jso-impl"]

View File

@@ -11,7 +11,6 @@ import dan200.computercraft.api.ComputerCraftAPI;
import dan200.computercraft.api.turtle.TurtleSide;
import dan200.computercraft.client.platform.ClientPlatformHelper;
import dan200.computercraft.client.turtle.TurtleUpgradeModellers;
import dan200.computercraft.shared.computer.core.ComputerFamily;
import dan200.computercraft.shared.turtle.blocks.TurtleBlockEntity;
import dan200.computercraft.shared.util.Holiday;
import net.minecraft.client.Minecraft;
@@ -21,7 +20,6 @@ import net.minecraft.client.renderer.blockentity.BlockEntityRenderDispatcher;
import net.minecraft.client.renderer.blockentity.BlockEntityRenderer;
import net.minecraft.client.renderer.blockentity.BlockEntityRendererProvider;
import net.minecraft.client.resources.model.BakedModel;
import net.minecraft.client.resources.model.ModelResourceLocation;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.world.phys.BlockHitResult;
import net.minecraft.world.phys.HitResult;
@@ -29,8 +27,6 @@ import net.minecraft.world.phys.HitResult;
import javax.annotation.Nullable;
public class TurtleBlockEntityRenderer implements BlockEntityRenderer<TurtleBlockEntity> {
private static final ModelResourceLocation NORMAL_TURTLE_MODEL = new ModelResourceLocation(ComputerCraftAPI.MOD_ID, "turtle_normal", "inventory");
private static final ModelResourceLocation ADVANCED_TURTLE_MODEL = new ModelResourceLocation(ComputerCraftAPI.MOD_ID, "turtle_advanced", "inventory");
private static final ResourceLocation COLOUR_TURTLE_MODEL = new ResourceLocation(ComputerCraftAPI.MOD_ID, "block/turtle_colour");
private static final ResourceLocation ELF_OVERLAY_MODEL = new ResourceLocation(ComputerCraftAPI.MOD_ID, "block/turtle_elf_overlay");
@@ -42,13 +38,6 @@ public class TurtleBlockEntityRenderer implements BlockEntityRenderer<TurtleBloc
font = context.getFont();
}
public static ResourceLocation getTurtleModel(ComputerFamily family, boolean coloured) {
return switch (family) {
default -> coloured ? COLOUR_TURTLE_MODEL : NORMAL_TURTLE_MODEL;
case ADVANCED -> coloured ? COLOUR_TURTLE_MODEL : ADVANCED_TURTLE_MODEL;
};
}
public static @Nullable ResourceLocation getTurtleOverlayModel(@Nullable ResourceLocation overlay, boolean christmas) {
if (overlay != null) return overlay;
if (christmas) return ELF_OVERLAY_MODEL;
@@ -78,7 +67,6 @@ public class TurtleBlockEntityRenderer implements BlockEntityRenderer<TurtleBloc
var matrix = transform.last().pose();
var opacity = (int) (mc.options.getBackgroundOpacity(0.25f) * 255) << 24;
var width = -font.width(label) / 2.0f;
// TODO: Check this looks okay
font.drawInBatch(label, width, (float) 0, 0x20ffffff, false, matrix, buffers, Font.DisplayMode.SEE_THROUGH, opacity, lightmapCoord);
font.drawInBatch(label, width, (float) 0, 0xffffffff, false, matrix, buffers, Font.DisplayMode.NORMAL, 0, lightmapCoord);
@@ -96,10 +84,18 @@ public class TurtleBlockEntityRenderer implements BlockEntityRenderer<TurtleBloc
// Render the turtle
var colour = turtle.getColour();
var family = turtle.getFamily();
var overlay = turtle.getOverlay();
renderModel(transform, buffers, lightmapCoord, overlayLight, getTurtleModel(family, colour != -1), colour == -1 ? null : new int[]{ colour });
if (colour == -1) {
// Render the turtle using its item model.
var modelManager = Minecraft.getInstance().getItemRenderer().getItemModelShaper();
var model = modelManager.getItemModel(turtle.getBlockState().getBlock().asItem());
if (model == null) model = modelManager.getModelManager().getMissingModel();
renderModel(transform, buffers, lightmapCoord, overlayLight, model, null);
} else {
// Otherwise render it using the colour item.
renderModel(transform, buffers, lightmapCoord, overlayLight, COLOUR_TURTLE_MODEL, new int[]{ colour });
}
// Render the overlay
var overlayModel = getTurtleOverlayModel(overlay, Holiday.getCurrent() == Holiday.CHRISTMAS);

View File

@@ -13,7 +13,6 @@ import dan200.computercraft.api.upgrades.UpgradeData;
import dan200.computercraft.core.util.Colour;
import dan200.computercraft.shared.ModRegistry;
import dan200.computercraft.shared.common.IColouredItem;
import dan200.computercraft.shared.computer.core.ComputerFamily;
import dan200.computercraft.shared.platform.PlatformHelper;
import dan200.computercraft.shared.platform.RecipeIngredients;
import dan200.computercraft.shared.platform.RegistryWrappers;
@@ -38,7 +37,6 @@ import net.minecraft.world.level.ItemLike;
import net.minecraft.world.level.block.Blocks;
import java.util.List;
import java.util.Locale;
import java.util.UUID;
import java.util.function.Consumer;
@@ -106,14 +104,13 @@ class RecipeProvider extends net.minecraft.data.recipes.RecipeProvider {
private void turtleUpgrades(Consumer<FinishedRecipe> add) {
for (var turtleItem : turtleItems()) {
var base = turtleItem.create(-1, null, -1, null, null, 0, null);
var nameId = turtleItem.getFamily().name().toLowerCase(Locale.ROOT);
var name = RegistryWrappers.ITEMS.getKey(turtleItem);
for (var upgrade : turtleUpgrades.getGeneratedUpgrades()) {
var result = turtleItem.create(-1, null, -1, null, UpgradeData.ofDefault(upgrade), -1, null);
ShapedRecipeBuilder
.shaped(RecipeCategory.REDSTONE, result.getItem())
.group(String.format("%s:turtle_%s", ComputerCraftAPI.MOD_ID, nameId))
.group(name.toString())
.pattern("#T")
.define('T', base.getItem())
.define('#', upgrade.getCraftingItem().getItem())
@@ -121,9 +118,7 @@ class RecipeProvider extends net.minecraft.data.recipes.RecipeProvider {
inventoryChange(base.getItem(), upgrade.getCraftingItem().getItem()))
.save(
RecipeWrapper.wrap(ModRegistry.RecipeSerializers.IMPOSTOR_SHAPED.get(), add).withResultTag(result.getTag()),
new ResourceLocation(ComputerCraftAPI.MOD_ID, String.format("turtle_%s/%s/%s",
nameId, upgrade.getUpgradeID().getNamespace(), upgrade.getUpgradeID().getPath()
))
name.withSuffix(String.format("/%s/%s", upgrade.getUpgradeID().getNamespace(), upgrade.getUpgradeID().getPath()))
);
}
}
@@ -141,15 +136,13 @@ class RecipeProvider extends net.minecraft.data.recipes.RecipeProvider {
private void pocketUpgrades(Consumer<FinishedRecipe> add) {
for (var pocket : pocketComputerItems()) {
var base = pocket.create(-1, null, -1, null);
if (base.isEmpty()) continue;
var nameId = pocket.getFamily().name().toLowerCase(Locale.ROOT);
var name = RegistryWrappers.ITEMS.getKey(pocket).withPath(x -> x.replace("pocket_computer_", "pocket_"));
for (var upgrade : pocketUpgrades.getGeneratedUpgrades()) {
var result = pocket.create(-1, null, -1, UpgradeData.ofDefault(upgrade));
ShapedRecipeBuilder
.shaped(RecipeCategory.REDSTONE, result.getItem())
.group(String.format("%s:pocket_%s", ComputerCraftAPI.MOD_ID, nameId))
.group(name.toString())
.pattern("#")
.pattern("P")
.define('P', base.getItem())
@@ -158,9 +151,7 @@ class RecipeProvider extends net.minecraft.data.recipes.RecipeProvider {
inventoryChange(base.getItem(), upgrade.getCraftingItem().getItem()))
.save(
RecipeWrapper.wrap(ModRegistry.RecipeSerializers.IMPOSTOR_SHAPED.get(), add).withResultTag(result.getTag()),
new ResourceLocation(ComputerCraftAPI.MOD_ID, String.format("pocket_%s/%s/%s",
nameId, upgrade.getUpgradeID().getNamespace(), upgrade.getUpgradeID().getPath()
))
name.withSuffix(String.format("/%s/%s", upgrade.getUpgradeID().getNamespace(), upgrade.getUpgradeID().getPath()))
);
}
}
@@ -190,12 +181,10 @@ class RecipeProvider extends net.minecraft.data.recipes.RecipeProvider {
private void turtleOverlay(Consumer<FinishedRecipe> add, String overlay, Consumer<ShapelessRecipeBuilder> build) {
for (var turtleItem : turtleItems()) {
var base = turtleItem.create(-1, null, -1, null, null, 0, null);
var nameId = turtleItem.getFamily().name().toLowerCase(Locale.ROOT);
var group = "%s:turtle_%s_overlay".formatted(ComputerCraftAPI.MOD_ID, nameId);
var name = RegistryWrappers.ITEMS.getKey(turtleItem);
var builder = ShapelessRecipeBuilder.shapeless(RecipeCategory.REDSTONE, base.getItem())
.group(group)
.group(name.withSuffix("_overlay").toString())
.unlockedBy("has_turtle", inventoryChange(base.getItem()));
build.accept(builder);
builder
@@ -204,7 +193,7 @@ class RecipeProvider extends net.minecraft.data.recipes.RecipeProvider {
RecipeWrapper
.wrap(ModRegistry.RecipeSerializers.TURTLE_OVERLAY.get(), add)
.withExtraData(x -> x.addProperty("overlay", new ResourceLocation(ComputerCraftAPI.MOD_ID, "block/" + overlay).toString())),
new ResourceLocation(ComputerCraftAPI.MOD_ID, "turtle_%s_overlays/%s".formatted(nameId, overlay))
name.withSuffix("_overlays/" + overlay)
);
}
}
@@ -253,7 +242,7 @@ class RecipeProvider extends net.minecraft.data.recipes.RecipeProvider {
.define('C', ModRegistry.Items.COMPUTER_NORMAL.get())
.unlockedBy("has_components", inventoryChange(itemPredicate(ModRegistry.Items.COMPUTER_NORMAL.get()), itemPredicate(ingredients.goldIngot())))
.save(
RecipeWrapper.wrap(ModRegistry.RecipeSerializers.COMPUTER_UPGRADE.get(), add).withExtraData(family(ComputerFamily.ADVANCED)),
RecipeWrapper.wrap(ModRegistry.RecipeSerializers.COMPUTER_UPGRADE.get(), add),
new ResourceLocation(ComputerCraftAPI.MOD_ID, "computer_advanced_upgrade")
);
@@ -277,7 +266,7 @@ class RecipeProvider extends net.minecraft.data.recipes.RecipeProvider {
.define('C', ModRegistry.Items.COMPUTER_NORMAL.get())
.define('I', ingredients.woodenChest())
.unlockedBy("has_computer", inventoryChange(ModRegistry.Items.COMPUTER_NORMAL.get()))
.save(RecipeWrapper.wrap(ModRegistry.RecipeSerializers.TURTLE.get(), add).withExtraData(family(ComputerFamily.NORMAL)));
.save(RecipeWrapper.wrap(ModRegistry.RecipeSerializers.TURTLE.get(), add));
ShapedRecipeBuilder
.shaped(RecipeCategory.REDSTONE, ModRegistry.Blocks.TURTLE_ADVANCED.get())
@@ -288,7 +277,7 @@ class RecipeProvider extends net.minecraft.data.recipes.RecipeProvider {
.define('C', ModRegistry.Items.COMPUTER_ADVANCED.get())
.define('I', ingredients.woodenChest())
.unlockedBy("has_computer", inventoryChange(ModRegistry.Items.COMPUTER_NORMAL.get()))
.save(RecipeWrapper.wrap(ModRegistry.RecipeSerializers.TURTLE.get(), add).withExtraData(family(ComputerFamily.ADVANCED)));
.save(RecipeWrapper.wrap(ModRegistry.RecipeSerializers.TURTLE.get(), add));
ShapedRecipeBuilder
.shaped(RecipeCategory.REDSTONE, ModRegistry.Blocks.TURTLE_ADVANCED.get())
@@ -300,7 +289,7 @@ class RecipeProvider extends net.minecraft.data.recipes.RecipeProvider {
.define('B', ingredients.goldBlock())
.unlockedBy("has_components", inventoryChange(itemPredicate(ModRegistry.Items.TURTLE_NORMAL.get()), itemPredicate(ingredients.goldIngot())))
.save(
RecipeWrapper.wrap(ModRegistry.RecipeSerializers.COMPUTER_UPGRADE.get(), add).withExtraData(family(ComputerFamily.ADVANCED)),
RecipeWrapper.wrap(ModRegistry.RecipeSerializers.COMPUTER_UPGRADE.get(), add),
new ResourceLocation(ComputerCraftAPI.MOD_ID, "turtle_advanced_upgrade")
);
@@ -367,7 +356,7 @@ class RecipeProvider extends net.minecraft.data.recipes.RecipeProvider {
.define('C', ModRegistry.Items.POCKET_COMPUTER_NORMAL.get())
.unlockedBy("has_components", inventoryChange(itemPredicate(ModRegistry.Items.POCKET_COMPUTER_NORMAL.get()), itemPredicate(ingredients.goldIngot())))
.save(
RecipeWrapper.wrap(ModRegistry.RecipeSerializers.COMPUTER_UPGRADE.get(), add).withExtraData(family(ComputerFamily.ADVANCED)),
RecipeWrapper.wrap(ModRegistry.RecipeSerializers.COMPUTER_UPGRADE.get(), add),
new ResourceLocation(ComputerCraftAPI.MOD_ID, "pocket_computer_advanced_upgrade")
);
@@ -519,10 +508,6 @@ class RecipeProvider extends net.minecraft.data.recipes.RecipeProvider {
return tag;
}
private static Consumer<JsonObject> family(ComputerFamily family) {
return json -> json.addProperty("family", family.getSerializedName());
}
private static void addSpecial(Consumer<FinishedRecipe> add, SimpleCraftingRecipeSerializer<?> special) {
SpecialRecipeBuilder.special(special).save(add, RegistryWrappers.RECIPE_SERIALIZERS.getKey(special).toString());
}

View File

@@ -79,6 +79,8 @@ class TagProvider {
ModRegistry.Blocks.WIRED_MODEM_FULL.get(),
ModRegistry.Blocks.CABLE.get()
);
tags.tag(BlockTags.WITHER_IMMUNE).add(ModRegistry.Blocks.COMPUTER_COMMAND.get());
}
public static void itemTags(ItemTagConsumer tags) {

View File

@@ -14,12 +14,15 @@ import dan200.computercraft.shared.computer.metrics.ComputerMBean;
import dan200.computercraft.shared.peripheral.monitor.MonitorWatcher;
import dan200.computercraft.shared.util.DropConsumer;
import dan200.computercraft.shared.util.TickScheduler;
import net.minecraft.resources.ResourceKey;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.server.MinecraftServer;
import net.minecraft.server.dedicated.DedicatedServer;
import net.minecraft.server.level.ServerPlayer;
import net.minecraft.server.packs.resources.PreparableReloadListener;
import net.minecraft.world.entity.Entity;
import net.minecraft.world.item.CreativeModeTab;
import net.minecraft.world.item.CreativeModeTabs;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.level.chunk.LevelChunk;
import net.minecraft.world.level.storage.loot.BuiltInLootTables;
@@ -111,4 +114,17 @@ public final class CommonHooks {
public static boolean onLivingDrop(Entity entity, ItemStack stack) {
return DropConsumer.onLivingDrop(entity, stack);
}
/**
* Add items to an existing creative tab.
*
* @param key The {@link ResourceKey} for this creative tab.
* @param context Additional parameters used for building the contents.
* @param out The creative tab output to append items to.
*/
public static void onBuildCreativeTab(ResourceKey<CreativeModeTab> key, CreativeModeTab.ItemDisplayParameters context, CreativeModeTab.Output out) {
if (key == CreativeModeTabs.OP_BLOCKS && context.hasPermissions()) {
out.accept(ModRegistry.Items.COMPUTER_COMMAND.get());
}
}
}

View File

@@ -34,6 +34,7 @@ import dan200.computercraft.shared.computer.inventory.ViewComputerMenu;
import dan200.computercraft.shared.computer.items.CommandComputerItem;
import dan200.computercraft.shared.computer.items.ComputerItem;
import dan200.computercraft.shared.computer.recipe.ComputerUpgradeRecipe;
import dan200.computercraft.shared.config.Config;
import dan200.computercraft.shared.data.BlockNamedEntityLootCondition;
import dan200.computercraft.shared.data.ConstantLootConditionSerializer;
import dan200.computercraft.shared.data.HasComputerIdLootCondition;
@@ -139,19 +140,17 @@ public final class ModRegistry {
}
public static final RegistryEntry<ComputerBlock<ComputerBlockEntity>> COMPUTER_NORMAL = REGISTRY.register("computer_normal",
() -> new ComputerBlock<>(computerProperties().mapColor(MapColor.STONE), ComputerFamily.NORMAL, BlockEntities.COMPUTER_NORMAL));
() -> new ComputerBlock<>(computerProperties().mapColor(MapColor.STONE), BlockEntities.COMPUTER_NORMAL));
public static final RegistryEntry<ComputerBlock<ComputerBlockEntity>> COMPUTER_ADVANCED = REGISTRY.register("computer_advanced",
() -> new ComputerBlock<>(computerProperties().mapColor(MapColor.GOLD), ComputerFamily.ADVANCED, BlockEntities.COMPUTER_ADVANCED));
() -> new ComputerBlock<>(computerProperties().mapColor(MapColor.GOLD), BlockEntities.COMPUTER_ADVANCED));
public static final RegistryEntry<ComputerBlock<CommandComputerBlockEntity>> COMPUTER_COMMAND = REGISTRY.register("computer_command", () -> new CommandComputerBlock<>(
computerProperties().strength(-1, 6000000.0F),
ComputerFamily.COMMAND, BlockEntities.COMPUTER_COMMAND
));
public static final RegistryEntry<ComputerBlock<CommandComputerBlockEntity>> COMPUTER_COMMAND = REGISTRY.register("computer_command",
() -> new CommandComputerBlock<>(computerProperties().strength(-1, 6000000.0F), BlockEntities.COMPUTER_COMMAND));
public static final RegistryEntry<TurtleBlock> TURTLE_NORMAL = REGISTRY.register("turtle_normal",
() -> new TurtleBlock(turtleProperties().mapColor(MapColor.STONE), ComputerFamily.NORMAL, BlockEntities.TURTLE_NORMAL));
() -> new TurtleBlock(turtleProperties().mapColor(MapColor.STONE), BlockEntities.TURTLE_NORMAL));
public static final RegistryEntry<TurtleBlock> TURTLE_ADVANCED = REGISTRY.register("turtle_advanced",
() -> new TurtleBlock(turtleProperties().mapColor(MapColor.GOLD), ComputerFamily.ADVANCED, BlockEntities.TURTLE_ADVANCED));
() -> new TurtleBlock(turtleProperties().mapColor(MapColor.GOLD).explosionResistance(TurtleBlock.IMMUNE_EXPLOSION_RESISTANCE), BlockEntities.TURTLE_ADVANCED));
public static final RegistryEntry<SpeakerBlock> SPEAKER = REGISTRY.register("speaker", () -> new SpeakerBlock(properties().mapColor(MapColor.STONE)));
public static final RegistryEntry<DiskDriveBlock> DISK_DRIVE = REGISTRY.register("disk_drive", () -> new DiskDriveBlock(properties().mapColor(MapColor.STONE)));
@@ -192,9 +191,9 @@ public final class ModRegistry {
ofBlock(Blocks.COMPUTER_COMMAND, (p, s) -> new CommandComputerBlockEntity(BlockEntities.COMPUTER_COMMAND.get(), p, s));
public static final RegistryEntry<BlockEntityType<TurtleBlockEntity>> TURTLE_NORMAL =
ofBlock(Blocks.TURTLE_NORMAL, (p, s) -> new TurtleBlockEntity(BlockEntities.TURTLE_NORMAL.get(), p, s, ComputerFamily.NORMAL));
ofBlock(Blocks.TURTLE_NORMAL, (p, s) -> new TurtleBlockEntity(BlockEntities.TURTLE_NORMAL.get(), p, s, () -> Config.turtleFuelLimit, ComputerFamily.NORMAL));
public static final RegistryEntry<BlockEntityType<TurtleBlockEntity>> TURTLE_ADVANCED =
ofBlock(Blocks.TURTLE_ADVANCED, (p, s) -> new TurtleBlockEntity(BlockEntities.TURTLE_ADVANCED.get(), p, s, ComputerFamily.ADVANCED));
ofBlock(Blocks.TURTLE_ADVANCED, (p, s) -> new TurtleBlockEntity(BlockEntities.TURTLE_ADVANCED.get(), p, s, () -> Config.advancedTurtleFuelLimit, ComputerFamily.ADVANCED));
public static final RegistryEntry<BlockEntityType<SpeakerBlockEntity>> SPEAKER =
ofBlock(Blocks.SPEAKER, (p, s) -> new SpeakerBlockEntity(BlockEntities.SPEAKER.get(), p, s));
@@ -311,7 +310,10 @@ public final class ModRegistry {
() -> new MenuType<>(PrinterMenu::new, FeatureFlags.VANILLA_SET));
public static final RegistryEntry<MenuType<HeldItemMenu>> PRINTOUT = REGISTRY.register("printout",
() -> ContainerData.toType(HeldItemContainerData::new, HeldItemMenu::createPrintout));
() -> ContainerData.toType(
HeldItemContainerData::new,
(id, inventory, data) -> new HeldItemMenu(Menus.PRINTOUT.get(), id, inventory.player, data.getHand())
));
public static final RegistryEntry<MenuType<ViewComputerMenu>> VIEW_COMPUTER = REGISTRY.register("view_computer",
() -> ContainerData.toType(ComputerContainerData::new, ViewComputerMenu::new));
@@ -371,11 +373,11 @@ public final class ModRegistry {
public static final RegistryEntry<SimpleCraftingRecipeSerializer<ClearColourRecipe>> DYEABLE_ITEM_CLEAR = simple("clear_colour", ClearColourRecipe::new);
public static final RegistryEntry<RecipeSerializer<TurtleRecipe>> TURTLE = REGISTRY.register("turtle", () -> TurtleRecipe.validatingSerialiser(TurtleRecipe::of));
public static final RegistryEntry<SimpleCraftingRecipeSerializer<TurtleUpgradeRecipe>> TURTLE_UPGRADE = simple("turtle_upgrade", TurtleUpgradeRecipe::new);
public static final RegistryEntry<RecipeSerializer<TurtleOverlayRecipe>> TURTLE_OVERLAY = REGISTRY.register("turtle_overlay", TurtleOverlayRecipe.Serializer::new);
public static final RegistryEntry<RecipeSerializer<TurtleOverlayRecipe>> TURTLE_OVERLAY = REGISTRY.register("turtle_overlay", TurtleOverlayRecipe.Serialiser::new);
public static final RegistryEntry<SimpleCraftingRecipeSerializer<PocketComputerUpgradeRecipe>> POCKET_COMPUTER_UPGRADE = simple("pocket_computer_upgrade", PocketComputerUpgradeRecipe::new);
public static final RegistryEntry<SimpleCraftingRecipeSerializer<PrintoutRecipe>> PRINTOUT = simple("printout", PrintoutRecipe::new);
public static final RegistryEntry<SimpleCraftingRecipeSerializer<DiskRecipe>> DISK = simple("disk", DiskRecipe::new);
public static final RegistryEntry<RecipeSerializer<ComputerUpgradeRecipe>> COMPUTER_UPGRADE = REGISTRY.register("computer_upgrade", ComputerUpgradeRecipe.Serializer::new);
public static final RegistryEntry<RecipeSerializer<ComputerUpgradeRecipe>> COMPUTER_UPGRADE = REGISTRY.register("computer_upgrade", () -> CustomShapedRecipe.validatingSerialiser(ComputerUpgradeRecipe::of));
}
public static class Permissions {
@@ -464,8 +466,8 @@ public final class ModRegistry {
* Register any objects which must be done on the main thread.
*/
public static void registerMainThread() {
CauldronInteraction.WATER.put(ModRegistry.Items.TURTLE_NORMAL.get(), TurtleItem.CAULDRON_INTERACTION);
CauldronInteraction.WATER.put(ModRegistry.Items.TURTLE_ADVANCED.get(), TurtleItem.CAULDRON_INTERACTION);
CauldronInteraction.WATER.put(Items.TURTLE_NORMAL.get(), TurtleItem.CAULDRON_INTERACTION);
CauldronInteraction.WATER.put(Items.TURTLE_ADVANCED.get(), TurtleItem.CAULDRON_INTERACTION);
}
private static void addTurtle(CreativeModeTab.Output out, TurtleItem turtle) {

View File

@@ -4,8 +4,6 @@
package dan200.computercraft.shared.common;
import dan200.computercraft.shared.ModRegistry;
import dan200.computercraft.shared.network.container.HeldItemContainerData;
import net.minecraft.network.chat.Component;
import net.minecraft.world.InteractionHand;
import net.minecraft.world.MenuProvider;
@@ -28,10 +26,6 @@ public class HeldItemMenu extends AbstractContainerMenu {
stack = player.getItemInHand(hand).copy();
}
public static HeldItemMenu createPrintout(int id, Inventory inventory, HeldItemContainerData data) {
return new HeldItemMenu(ModRegistry.Menus.PRINTOUT.get(), id, inventory.player, data.getHand());
}
public ItemStack getStack() {
return stack;
}

View File

@@ -7,7 +7,6 @@ package dan200.computercraft.shared.computer.blocks;
import dan200.computercraft.annotations.ForgeOverride;
import dan200.computercraft.api.ComputerCraftAPI;
import dan200.computercraft.shared.common.IBundledRedstoneBlock;
import dan200.computercraft.shared.computer.core.ComputerFamily;
import dan200.computercraft.shared.computer.items.IComputerItem;
import dan200.computercraft.shared.platform.RegistryEntry;
import dan200.computercraft.shared.util.BlockEntityHelpers;
@@ -42,13 +41,11 @@ import javax.annotation.Nullable;
public abstract class AbstractComputerBlock<T extends AbstractComputerBlockEntity> extends HorizontalDirectionalBlock implements IBundledRedstoneBlock, EntityBlock {
private static final ResourceLocation DROP = new ResourceLocation(ComputerCraftAPI.MOD_ID, "computer");
private final ComputerFamily family;
protected final RegistryEntry<BlockEntityType<T>> type;
private final BlockEntityTicker<T> serverTicker = (level, pos, state, computer) -> computer.serverTick();
protected AbstractComputerBlock(Properties settings, ComputerFamily family, RegistryEntry<BlockEntityType<T>> type) {
protected AbstractComputerBlock(Properties settings, RegistryEntry<BlockEntityType<T>> type) {
super(settings);
this.family = family;
this.type = type;
}
@@ -82,10 +79,6 @@ public abstract class AbstractComputerBlock<T extends AbstractComputerBlockEntit
protected abstract ItemStack getItem(AbstractComputerBlockEntity tile);
public ComputerFamily getFamily() {
return family;
}
@Override
@Deprecated
public int getSignal(BlockState state, BlockGetter world, BlockPos pos, Direction incomingSide) {

View File

@@ -4,7 +4,6 @@
package dan200.computercraft.shared.computer.blocks;
import dan200.computercraft.shared.computer.core.ComputerFamily;
import dan200.computercraft.shared.platform.RegistryEntry;
import net.minecraft.world.level.block.GameMasterBlock;
import net.minecraft.world.level.block.entity.BlockEntityType;
@@ -17,7 +16,7 @@ import net.minecraft.world.level.block.entity.BlockEntityType;
* @see dan200.computercraft.shared.computer.items.CommandComputerItem
*/
public class CommandComputerBlock<T extends CommandComputerBlockEntity> extends ComputerBlock<T> implements GameMasterBlock {
public CommandComputerBlock(Properties settings, ComputerFamily family, RegistryEntry<BlockEntityType<T>> type) {
super(settings, family, type);
public CommandComputerBlock(Properties settings, RegistryEntry<BlockEntityType<T>> type) {
super(settings, type);
}
}

View File

@@ -4,9 +4,8 @@
package dan200.computercraft.shared.computer.blocks;
import dan200.computercraft.shared.computer.core.ComputerFamily;
import dan200.computercraft.shared.computer.core.ComputerState;
import dan200.computercraft.shared.computer.items.ComputerItemFactory;
import dan200.computercraft.shared.computer.items.ComputerItem;
import dan200.computercraft.shared.platform.RegistryEntry;
import net.minecraft.core.Direction;
import net.minecraft.world.item.ItemStack;
@@ -25,8 +24,8 @@ public class ComputerBlock<T extends ComputerBlockEntity> extends AbstractComput
public static final EnumProperty<ComputerState> STATE = EnumProperty.create("state", ComputerState.class);
public static final DirectionProperty FACING = BlockStateProperties.HORIZONTAL_FACING;
public ComputerBlock(Properties settings, ComputerFamily family, RegistryEntry<BlockEntityType<T>> type) {
super(settings, family, type);
public ComputerBlock(Properties settings, RegistryEntry<BlockEntityType<T>> type) {
super(settings, type);
registerDefaultState(defaultBlockState()
.setValue(FACING, Direction.NORTH)
.setValue(STATE, ComputerState.OFF)
@@ -46,6 +45,9 @@ public class ComputerBlock<T extends ComputerBlockEntity> extends AbstractComput
@Override
protected ItemStack getItem(AbstractComputerBlockEntity tile) {
return tile instanceof ComputerBlockEntity ? ComputerItemFactory.create((ComputerBlockEntity) tile) : ItemStack.EMPTY;
if (!(tile instanceof ComputerBlockEntity computer)) return ItemStack.EMPTY;
if (!(asItem() instanceof ComputerItem item)) return ItemStack.EMPTY;
return item.create(computer.getComputerID(), computer.getLabel());
}
}

View File

@@ -32,11 +32,9 @@ public class ComputerBlockEntity extends AbstractComputerBlockEntity {
@Override
protected ServerComputer createComputer(int id) {
var family = getFamily();
return new ServerComputer(
(ServerLevel) getLevel(), getBlockPos(), id, label,
family, Config.computerTermWidth,
Config.computerTermHeight
getFamily(), Config.computerTermWidth, Config.computerTermHeight
);
}

View File

@@ -4,33 +4,8 @@
package dan200.computercraft.shared.computer.core;
import com.google.gson.JsonObject;
import com.google.gson.JsonSyntaxException;
import net.minecraft.util.GsonHelper;
import net.minecraft.util.StringRepresentable;
public enum ComputerFamily implements StringRepresentable {
NORMAL("normal"),
ADVANCED("advanced"),
COMMAND("command");
private final String name;
ComputerFamily(String name) {
this.name = name;
}
public static ComputerFamily getFamily(JsonObject json, String name) {
var familyName = GsonHelper.getAsString(json, name);
for (var family : values()) {
if (family.getSerializedName().equalsIgnoreCase(familyName)) return family;
}
throw new JsonSyntaxException("Unknown computer family '" + familyName + "' for field " + name);
}
@Override
public String getSerializedName() {
return name;
}
public enum ComputerFamily {
NORMAL,
ADVANCED,
COMMAND,
}

View File

@@ -8,7 +8,6 @@ import dan200.computercraft.api.ComputerCraftAPI;
import dan200.computercraft.api.filesystem.Mount;
import dan200.computercraft.api.media.IMedia;
import dan200.computercraft.shared.computer.blocks.AbstractComputerBlock;
import dan200.computercraft.shared.computer.core.ComputerFamily;
import dan200.computercraft.shared.config.Config;
import net.minecraft.ChatFormatting;
import net.minecraft.network.chat.Component;
@@ -22,11 +21,8 @@ import javax.annotation.Nullable;
import java.util.List;
public abstract class AbstractComputerItem extends BlockItem implements IComputerItem, IMedia {
private final ComputerFamily family;
public AbstractComputerItem(AbstractComputerBlock<?> block, Properties settings) {
super(block, settings);
family = block.getFamily();
}
@Override
@@ -45,13 +41,6 @@ public abstract class AbstractComputerItem extends BlockItem implements ICompute
return IComputerItem.super.getLabel(stack);
}
@Override
public final ComputerFamily getFamily() {
return family;
}
// IMedia implementation
@Override
public boolean setLabel(ItemStack stack, @Nullable String label) {
if (label != null) {

View File

@@ -5,8 +5,8 @@
package dan200.computercraft.shared.computer.items;
import dan200.computercraft.shared.computer.blocks.ComputerBlock;
import dan200.computercraft.shared.computer.core.ComputerFamily;
import net.minecraft.network.chat.Component;
import net.minecraft.world.item.Item;
import net.minecraft.world.item.ItemStack;
import javax.annotation.Nullable;
@@ -24,9 +24,9 @@ public class ComputerItem extends AbstractComputerItem {
}
@Override
public ItemStack withFamily(ItemStack stack, ComputerFamily family) {
var result = ComputerItemFactory.create(getComputerID(stack), null, family);
if (stack.hasCustomHoverName()) result.setHoverName(stack.getHoverName());
return result;
public ItemStack changeItem(ItemStack stack, Item newItem) {
return newItem instanceof ComputerItem computer
? computer.create(getComputerID(stack), getLabel(stack))
: ItemStack.EMPTY;
}
}

View File

@@ -1,29 +0,0 @@
// Copyright Daniel Ratcliffe, 2011-2022. Do not distribute without permission.
//
// SPDX-License-Identifier: LicenseRef-CCPL
package dan200.computercraft.shared.computer.items;
import dan200.computercraft.shared.ModRegistry;
import dan200.computercraft.shared.computer.blocks.ComputerBlockEntity;
import dan200.computercraft.shared.computer.core.ComputerFamily;
import net.minecraft.world.item.ItemStack;
import javax.annotation.Nullable;
public final class ComputerItemFactory {
private ComputerItemFactory() {
}
public static ItemStack create(ComputerBlockEntity tile) {
return create(tile.getComputerID(), tile.getLabel(), tile.getFamily());
}
public static ItemStack create(int id, @Nullable String label, ComputerFamily family) {
return switch (family) {
case NORMAL -> ModRegistry.Items.COMPUTER_NORMAL.get().create(id, label);
case ADVANCED -> ModRegistry.Items.COMPUTER_ADVANCED.get().create(id, label);
case COMMAND -> ModRegistry.Items.COMPUTER_COMMAND.get().create(id, label);
};
}
}

View File

@@ -4,7 +4,7 @@
package dan200.computercraft.shared.computer.items;
import dan200.computercraft.shared.computer.core.ComputerFamily;
import net.minecraft.world.item.Item;
import net.minecraft.world.item.ItemStack;
import javax.annotation.Nullable;
@@ -21,7 +21,15 @@ public interface IComputerItem {
return stack.hasCustomHoverName() ? stack.getHoverName().getString() : null;
}
ComputerFamily getFamily();
ItemStack withFamily(ItemStack stack, ComputerFamily family);
/**
* Create a new stack, changing the underlying item.
* <p>
* This should copy the computer's data to a different item of the same type (for instance, converting a normal
* computer to an advanced one).
*
* @param stack The current computer stack.
* @param newItem The new item.
* @return The new stack, possibly {@linkplain ItemStack#EMPTY empty} if {@code newItem} is of the same type.
*/
ItemStack changeItem(ItemStack stack, Item newItem);
}

View File

@@ -4,57 +4,44 @@
package dan200.computercraft.shared.computer.recipe;
import com.google.gson.JsonObject;
import com.mojang.serialization.DataResult;
import dan200.computercraft.shared.ModRegistry;
import dan200.computercraft.shared.computer.core.ComputerFamily;
import dan200.computercraft.shared.computer.items.IComputerItem;
import dan200.computercraft.shared.recipe.ShapedRecipeSpec;
import net.minecraft.network.FriendlyByteBuf;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.world.item.Item;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.item.crafting.RecipeSerializer;
/**
* A recipe which "upgrades" a {@linkplain IComputerItem computer}, converting it from one {@linkplain ComputerFamily
* family} to another.
* A recipe which "upgrades" a {@linkplain IComputerItem computer}, converting to it a new item (for instance a normal
* turtle to an advanced one).
*
* @see IComputerItem#changeItem(ItemStack, Item)
*/
public final class ComputerUpgradeRecipe extends ComputerConvertRecipe {
private final ComputerFamily family;
private final Item result;
private ComputerUpgradeRecipe(ResourceLocation identifier, ShapedRecipeSpec recipe, ComputerFamily family) {
private ComputerUpgradeRecipe(ResourceLocation identifier, ShapedRecipeSpec recipe) {
super(identifier, recipe);
this.family = family;
this.result = recipe.result().getItem();
}
public static DataResult<ComputerUpgradeRecipe> of(ResourceLocation id, ShapedRecipeSpec recipe) {
if (!(recipe.result().getItem() instanceof IComputerItem)) {
return DataResult.error(() -> recipe.result().getItem() + " is not a computer item");
}
return DataResult.success(new ComputerUpgradeRecipe(id, recipe));
}
@Override
protected ItemStack convert(IComputerItem item, ItemStack stack) {
return item.withFamily(stack, family);
return item.changeItem(stack, result);
}
@Override
public RecipeSerializer<ComputerUpgradeRecipe> getSerializer() {
return ModRegistry.RecipeSerializers.COMPUTER_UPGRADE.get();
}
public static class Serializer implements RecipeSerializer<ComputerUpgradeRecipe> {
@Override
public ComputerUpgradeRecipe fromJson(ResourceLocation identifier, JsonObject json) {
var recipe = ShapedRecipeSpec.fromJson(json);
var family = ComputerFamily.getFamily(json, "family");
return new ComputerUpgradeRecipe(identifier, recipe, family);
}
@Override
public ComputerUpgradeRecipe fromNetwork(ResourceLocation identifier, FriendlyByteBuf buf) {
var recipe = ShapedRecipeSpec.fromNetwork(buf);
var family = buf.readEnum(ComputerFamily.class);
return new ComputerUpgradeRecipe(identifier, recipe, family);
}
@Override
public void toNetwork(FriendlyByteBuf buf, ComputerUpgradeRecipe recipe) {
recipe.toSpec().toNetwork(buf);
buf.writeEnum(recipe.family);
}
}
}

View File

@@ -0,0 +1,18 @@
// SPDX-FileCopyrightText: 2023 The CC: Tweaked Developers
//
// SPDX-License-Identifier: MPL-2.0
package dan200.computercraft.shared.network;
/**
* A type of message to send over the network.
* <p>
* Much like recipe or argument serialisers, each type of {@link NetworkMessage} should have a unique type associated
* with it. This holds platform-specific information about how the packet should be sent over the network.
*
* @param <T> The type of message to send
* @see NetworkMessages
* @see NetworkMessage#type()
*/
public interface MessageType<T extends NetworkMessage<?>> {
}

View File

@@ -17,6 +17,13 @@ import net.minecraft.network.FriendlyByteBuf;
* @see ServerNetworkContext
*/
public interface NetworkMessage<T> {
/**
* Get the type of this message.
*
* @return The type of this message.
*/
MessageType<?> type();
/**
* Write this packet to a buffer.
* <p>
@@ -24,7 +31,7 @@ public interface NetworkMessage<T> {
*
* @param buf The buffer to write data to.
*/
void toBytes(FriendlyByteBuf buf);
void write(FriendlyByteBuf buf);
/**
* Handle this {@link NetworkMessage}.

View File

@@ -4,48 +4,84 @@
package dan200.computercraft.shared.network;
import dan200.computercraft.api.ComputerCraftAPI;
import dan200.computercraft.shared.network.client.*;
import dan200.computercraft.shared.network.server.*;
import dan200.computercraft.shared.platform.PlatformHelper;
import it.unimi.dsi.fastutil.ints.IntOpenHashSet;
import it.unimi.dsi.fastutil.ints.IntSet;
import net.minecraft.network.FriendlyByteBuf;
import net.minecraft.resources.ResourceLocation;
import java.util.function.Function;
import java.util.*;
/**
* Registry for all packets provided by CC: Tweaked.
* List of all {@link MessageType}s provided by CC: Tweaked.
*
* @see PlatformHelper The platform helper is used to send packets.
*/
public final class NetworkMessages {
private static final IntSet seenIds = new IntOpenHashSet();
private static final Set<String> seenChannel = new HashSet<>();
private static final List<MessageType<? extends NetworkMessage<ServerNetworkContext>>> serverMessages = new ArrayList<>();
private static final List<MessageType<? extends NetworkMessage<ClientNetworkContext>>> clientMessages = new ArrayList<>();
public static final MessageType<ComputerActionServerMessage> COMPUTER_ACTION = registerServerbound(0, "computer_action", ComputerActionServerMessage.class, ComputerActionServerMessage::new);
public static final MessageType<QueueEventServerMessage> QUEUE_EVENT = registerServerbound(1, "queue_event", QueueEventServerMessage.class, QueueEventServerMessage::new);
public static final MessageType<KeyEventServerMessage> KEY_EVENT = registerServerbound(2, "key_event", KeyEventServerMessage.class, KeyEventServerMessage::new);
public static final MessageType<MouseEventServerMessage> MOUSE_EVENT = registerServerbound(3, "mouse_event", MouseEventServerMessage.class, MouseEventServerMessage::new);
public static final MessageType<UploadFileMessage> UPLOAD_FILE = registerServerbound(4, "upload_file", UploadFileMessage.class, UploadFileMessage::new);
public static final MessageType<ChatTableClientMessage> CHAT_TABLE = registerClientbound(10, "chat_table", ChatTableClientMessage.class, ChatTableClientMessage::new);
public static final MessageType<PocketComputerDataMessage> POCKET_COMPUTER_DATA = registerClientbound(11, "pocket_computer_data", PocketComputerDataMessage.class, PocketComputerDataMessage::new);
public static final MessageType<PocketComputerDeletedClientMessage> POCKET_COMPUTER_DELETED = registerClientbound(12, "pocket_computer_deleted", PocketComputerDeletedClientMessage.class, PocketComputerDeletedClientMessage::new);
public static final MessageType<ComputerTerminalClientMessage> COMPUTER_TERMINAL = registerClientbound(13, "computer_terminal", ComputerTerminalClientMessage.class, ComputerTerminalClientMessage::new);
public static final MessageType<PlayRecordClientMessage> PLAY_RECORD = registerClientbound(14, "play_record", PlayRecordClientMessage.class, PlayRecordClientMessage::new);
public static final MessageType<MonitorClientMessage> MONITOR_CLIENT = registerClientbound(15, "monitor_client", MonitorClientMessage.class, MonitorClientMessage::new);
public static final MessageType<SpeakerAudioClientMessage> SPEAKER_AUDIO = registerClientbound(16, "speaker_audio", SpeakerAudioClientMessage.class, SpeakerAudioClientMessage::new);
public static final MessageType<SpeakerMoveClientMessage> SPEAKER_MOVE = registerClientbound(17, "speaker_move", SpeakerMoveClientMessage.class, SpeakerMoveClientMessage::new);
public static final MessageType<SpeakerPlayClientMessage> SPEAKER_PLAY = registerClientbound(18, "speaker_play", SpeakerPlayClientMessage.class, SpeakerPlayClientMessage::new);
public static final MessageType<SpeakerStopClientMessage> SPEAKER_STOP = registerClientbound(19, "speaker_stop", SpeakerStopClientMessage.class, SpeakerStopClientMessage::new);
public static final MessageType<UploadResultMessage> UPLOAD_RESULT = registerClientbound(20, "upload_result", UploadResultMessage.class, UploadResultMessage::new);
public static final MessageType<UpgradesLoadedMessage> UPGRADES_LOADED = registerClientbound(21, "upgrades_loaded", UpgradesLoadedMessage.class, UpgradesLoadedMessage::new);
private NetworkMessages() {
}
public interface PacketRegistry {
<T extends NetworkMessage<ClientNetworkContext>> void registerClientbound(int id, Class<T> type, Function<FriendlyByteBuf, T> decoder);
<T extends NetworkMessage<ServerNetworkContext>> void registerServerbound(int id, Class<T> type, Function<FriendlyByteBuf, T> decoder);
private static <C, T extends NetworkMessage<C>> MessageType<T> register(
List<MessageType<? extends NetworkMessage<C>>> messages,
int id, String channel, Class<T> klass, FriendlyByteBuf.Reader<T> reader
) {
if (!seenIds.add(id)) throw new IllegalArgumentException("Duplicate id " + id);
if (!seenChannel.add(channel)) throw new IllegalArgumentException("Duplicate channel " + channel);
var type = PlatformHelper.get().createMessageType(id, new ResourceLocation(ComputerCraftAPI.MOD_ID, channel), klass, reader);
messages.add(type);
return type;
}
public static void register(PacketRegistry registry) {
// Server messages
registry.registerServerbound(0, ComputerActionServerMessage.class, ComputerActionServerMessage::new);
registry.registerServerbound(1, QueueEventServerMessage.class, QueueEventServerMessage::new);
registry.registerServerbound(2, KeyEventServerMessage.class, KeyEventServerMessage::new);
registry.registerServerbound(3, MouseEventServerMessage.class, MouseEventServerMessage::new);
registry.registerServerbound(4, UploadFileMessage.class, UploadFileMessage::new);
private static <T extends NetworkMessage<ServerNetworkContext>> MessageType<T> registerServerbound(int id, String channel, Class<T> klass, FriendlyByteBuf.Reader<T> reader) {
return register(serverMessages, id, channel, klass, reader);
}
// Client messages
registry.registerClientbound(10, ChatTableClientMessage.class, ChatTableClientMessage::new);
registry.registerClientbound(11, PocketComputerDataMessage.class, PocketComputerDataMessage::new);
registry.registerClientbound(12, PocketComputerDeletedClientMessage.class, PocketComputerDeletedClientMessage::new);
registry.registerClientbound(13, ComputerTerminalClientMessage.class, ComputerTerminalClientMessage::new);
registry.registerClientbound(14, PlayRecordClientMessage.class, PlayRecordClientMessage::new);
registry.registerClientbound(15, MonitorClientMessage.class, MonitorClientMessage::new);
registry.registerClientbound(16, SpeakerAudioClientMessage.class, SpeakerAudioClientMessage::new);
registry.registerClientbound(17, SpeakerMoveClientMessage.class, SpeakerMoveClientMessage::new);
registry.registerClientbound(18, SpeakerPlayClientMessage.class, SpeakerPlayClientMessage::new);
registry.registerClientbound(19, SpeakerStopClientMessage.class, SpeakerStopClientMessage::new);
registry.registerClientbound(20, UploadResultMessage.class, UploadResultMessage::new);
registry.registerClientbound(21, UpgradesLoadedMessage.class, UpgradesLoadedMessage::new);
private static <T extends NetworkMessage<ClientNetworkContext>> MessageType<T> registerClientbound(int id, String channel, Class<T> klass, FriendlyByteBuf.Reader<T> reader) {
return register(clientMessages, id, channel, klass, reader);
}
/**
* Get all serverbound message types.
*
* @return An unmodifiable sequence of all serverbound message types.
*/
public static Collection<MessageType<? extends NetworkMessage<ServerNetworkContext>>> getServerbound() {
return Collections.unmodifiableCollection(serverMessages);
}
/**
* Get all clientbound message types.
*
* @return An unmodifiable sequence of all clientbound message types.
*/
public static Collection<MessageType<? extends NetworkMessage<ClientNetworkContext>>> getClientbound() {
return Collections.unmodifiableCollection(clientMessages);
}
}

View File

@@ -5,7 +5,9 @@
package dan200.computercraft.shared.network.client;
import dan200.computercraft.shared.command.text.TableBuilder;
import dan200.computercraft.shared.network.MessageType;
import dan200.computercraft.shared.network.NetworkMessage;
import dan200.computercraft.shared.network.NetworkMessages;
import net.minecraft.network.FriendlyByteBuf;
import net.minecraft.network.chat.Component;
@@ -43,7 +45,7 @@ public class ChatTableClientMessage implements NetworkMessage<ClientNetworkConte
}
@Override
public void toBytes(FriendlyByteBuf buf) {
public void write(FriendlyByteBuf buf) {
buf.writeUtf(table.getId(), MAX_LEN);
buf.writeVarInt(table.getColumns());
buf.writeBoolean(table.getHeaders() != null);
@@ -63,4 +65,9 @@ public class ChatTableClientMessage implements NetworkMessage<ClientNetworkConte
public void handle(ClientNetworkContext context) {
context.handleChatTable(table);
}
@Override
public MessageType<ChatTableClientMessage> type() {
return NetworkMessages.CHAT_TABLE;
}
}

View File

@@ -5,7 +5,9 @@
package dan200.computercraft.shared.network.client;
import dan200.computercraft.shared.computer.terminal.TerminalState;
import dan200.computercraft.shared.network.MessageType;
import dan200.computercraft.shared.network.NetworkMessage;
import dan200.computercraft.shared.network.NetworkMessages;
import net.minecraft.network.FriendlyByteBuf;
import net.minecraft.world.inventory.AbstractContainerMenu;
@@ -25,7 +27,7 @@ public class ComputerTerminalClientMessage implements NetworkMessage<ClientNetwo
}
@Override
public void toBytes(FriendlyByteBuf buf) {
public void write(FriendlyByteBuf buf) {
buf.writeVarInt(containerId);
terminal.write(buf);
}
@@ -34,4 +36,9 @@ public class ComputerTerminalClientMessage implements NetworkMessage<ClientNetwo
public void handle(ClientNetworkContext context) {
context.handleComputerTerminal(containerId, terminal);
}
@Override
public MessageType<ComputerTerminalClientMessage> type() {
return NetworkMessages.COMPUTER_TERMINAL;
}
}

View File

@@ -5,7 +5,9 @@
package dan200.computercraft.shared.network.client;
import dan200.computercraft.shared.computer.terminal.TerminalState;
import dan200.computercraft.shared.network.MessageType;
import dan200.computercraft.shared.network.NetworkMessage;
import dan200.computercraft.shared.network.NetworkMessages;
import net.minecraft.core.BlockPos;
import net.minecraft.network.FriendlyByteBuf;
@@ -25,7 +27,7 @@ public class MonitorClientMessage implements NetworkMessage<ClientNetworkContext
}
@Override
public void toBytes(FriendlyByteBuf buf) {
public void write(FriendlyByteBuf buf) {
buf.writeBlockPos(pos);
state.write(buf);
}
@@ -34,4 +36,9 @@ public class MonitorClientMessage implements NetworkMessage<ClientNetworkContext
public void handle(ClientNetworkContext context) {
context.handleMonitorData(pos, state);
}
@Override
public MessageType<MonitorClientMessage> type() {
return NetworkMessages.MONITOR_CLIENT;
}
}

View File

@@ -4,7 +4,9 @@
package dan200.computercraft.shared.network.client;
import dan200.computercraft.shared.network.MessageType;
import dan200.computercraft.shared.network.NetworkMessage;
import dan200.computercraft.shared.network.NetworkMessages;
import dan200.computercraft.shared.peripheral.diskdrive.DiskDriveBlockEntity;
import net.minecraft.core.BlockPos;
import net.minecraft.network.FriendlyByteBuf;
@@ -43,7 +45,7 @@ public class PlayRecordClientMessage implements NetworkMessage<ClientNetworkCont
}
@Override
public void toBytes(FriendlyByteBuf buf) {
public void write(FriendlyByteBuf buf) {
buf.writeBlockPos(pos);
buf.writeNullable(soundEvent, (b, e) -> e.writeToNetwork(b));
buf.writeNullable(name, FriendlyByteBuf::writeUtf);
@@ -53,4 +55,9 @@ public class PlayRecordClientMessage implements NetworkMessage<ClientNetworkCont
public void handle(ClientNetworkContext context) {
context.handlePlayRecord(pos, soundEvent, name);
}
@Override
public MessageType<PlayRecordClientMessage> type() {
return NetworkMessages.PLAY_RECORD;
}
}

View File

@@ -7,7 +7,9 @@ package dan200.computercraft.shared.network.client;
import dan200.computercraft.shared.computer.core.ComputerState;
import dan200.computercraft.shared.computer.terminal.NetworkedTerminal;
import dan200.computercraft.shared.computer.terminal.TerminalState;
import dan200.computercraft.shared.network.MessageType;
import dan200.computercraft.shared.network.NetworkMessage;
import dan200.computercraft.shared.network.NetworkMessages;
import dan200.computercraft.shared.pocket.core.PocketServerComputer;
import net.minecraft.network.FriendlyByteBuf;
@@ -35,7 +37,7 @@ public class PocketComputerDataMessage implements NetworkMessage<ClientNetworkCo
}
@Override
public void toBytes(FriendlyByteBuf buf) {
public void write(FriendlyByteBuf buf) {
buf.writeVarInt(instanceId);
buf.writeEnum(state);
buf.writeVarInt(lightState);
@@ -46,4 +48,9 @@ public class PocketComputerDataMessage implements NetworkMessage<ClientNetworkCo
public void handle(ClientNetworkContext context) {
context.handlePocketComputerData(instanceId, state, lightState, terminal);
}
@Override
public MessageType<PocketComputerDataMessage> type() {
return NetworkMessages.POCKET_COMPUTER_DATA;
}
}

View File

@@ -4,7 +4,9 @@
package dan200.computercraft.shared.network.client;
import dan200.computercraft.shared.network.MessageType;
import dan200.computercraft.shared.network.NetworkMessage;
import dan200.computercraft.shared.network.NetworkMessages;
import net.minecraft.network.FriendlyByteBuf;
@@ -20,7 +22,7 @@ public class PocketComputerDeletedClientMessage implements NetworkMessage<Client
}
@Override
public void toBytes(FriendlyByteBuf buf) {
public void write(FriendlyByteBuf buf) {
buf.writeVarInt(instanceId);
}
@@ -28,4 +30,9 @@ public class PocketComputerDeletedClientMessage implements NetworkMessage<Client
public void handle(ClientNetworkContext context) {
context.handlePocketComputerDeleted(instanceId);
}
@Override
public MessageType<PocketComputerDeletedClientMessage> type() {
return NetworkMessages.POCKET_COMPUTER_DELETED;
}
}

View File

@@ -4,7 +4,9 @@
package dan200.computercraft.shared.network.client;
import dan200.computercraft.shared.network.MessageType;
import dan200.computercraft.shared.network.NetworkMessage;
import dan200.computercraft.shared.network.NetworkMessages;
import dan200.computercraft.shared.peripheral.speaker.SpeakerBlockEntity;
import dan200.computercraft.shared.peripheral.speaker.SpeakerPosition;
import net.minecraft.network.FriendlyByteBuf;
@@ -47,7 +49,7 @@ public class SpeakerAudioClientMessage implements NetworkMessage<ClientNetworkCo
}
@Override
public void toBytes(FriendlyByteBuf buf) {
public void write(FriendlyByteBuf buf) {
buf.writeUUID(source);
pos.write(buf);
buf.writeFloat(volume);
@@ -58,4 +60,9 @@ public class SpeakerAudioClientMessage implements NetworkMessage<ClientNetworkCo
public void handle(ClientNetworkContext context) {
context.handleSpeakerAudio(source, pos, volume);
}
@Override
public MessageType<SpeakerAudioClientMessage> type() {
return NetworkMessages.SPEAKER_AUDIO;
}
}

View File

@@ -4,7 +4,9 @@
package dan200.computercraft.shared.network.client;
import dan200.computercraft.shared.network.MessageType;
import dan200.computercraft.shared.network.NetworkMessage;
import dan200.computercraft.shared.network.NetworkMessages;
import dan200.computercraft.shared.peripheral.speaker.SpeakerBlockEntity;
import dan200.computercraft.shared.peripheral.speaker.SpeakerPosition;
import net.minecraft.network.FriendlyByteBuf;
@@ -33,7 +35,7 @@ public class SpeakerMoveClientMessage implements NetworkMessage<ClientNetworkCon
}
@Override
public void toBytes(FriendlyByteBuf buf) {
public void write(FriendlyByteBuf buf) {
buf.writeUUID(source);
pos.write(buf);
}
@@ -42,4 +44,9 @@ public class SpeakerMoveClientMessage implements NetworkMessage<ClientNetworkCon
public void handle(ClientNetworkContext context) {
context.handleSpeakerMove(source, pos);
}
@Override
public MessageType<SpeakerMoveClientMessage> type() {
return NetworkMessages.SPEAKER_MOVE;
}
}

View File

@@ -4,7 +4,9 @@
package dan200.computercraft.shared.network.client;
import dan200.computercraft.shared.network.MessageType;
import dan200.computercraft.shared.network.NetworkMessage;
import dan200.computercraft.shared.network.NetworkMessages;
import dan200.computercraft.shared.peripheral.speaker.SpeakerBlockEntity;
import dan200.computercraft.shared.peripheral.speaker.SpeakerPosition;
import net.minecraft.network.FriendlyByteBuf;
@@ -43,7 +45,7 @@ public class SpeakerPlayClientMessage implements NetworkMessage<ClientNetworkCon
}
@Override
public void toBytes(FriendlyByteBuf buf) {
public void write(FriendlyByteBuf buf) {
buf.writeUUID(source);
pos.write(buf);
buf.writeResourceLocation(sound);
@@ -55,4 +57,9 @@ public class SpeakerPlayClientMessage implements NetworkMessage<ClientNetworkCon
public void handle(ClientNetworkContext context) {
context.handleSpeakerPlay(source, pos, sound, volume, pitch);
}
@Override
public MessageType<SpeakerPlayClientMessage> type() {
return NetworkMessages.SPEAKER_PLAY;
}
}

View File

@@ -4,7 +4,9 @@
package dan200.computercraft.shared.network.client;
import dan200.computercraft.shared.network.MessageType;
import dan200.computercraft.shared.network.NetworkMessage;
import dan200.computercraft.shared.network.NetworkMessages;
import dan200.computercraft.shared.peripheral.speaker.SpeakerBlockEntity;
import net.minecraft.network.FriendlyByteBuf;
@@ -29,7 +31,7 @@ public class SpeakerStopClientMessage implements NetworkMessage<ClientNetworkCon
}
@Override
public void toBytes(FriendlyByteBuf buf) {
public void write(FriendlyByteBuf buf) {
buf.writeUUID(source);
}
@@ -37,4 +39,9 @@ public class SpeakerStopClientMessage implements NetworkMessage<ClientNetworkCon
public void handle(ClientNetworkContext context) {
context.handleSpeakerStop(source);
}
@Override
public MessageType<SpeakerStopClientMessage> type() {
return NetworkMessages.SPEAKER_STOP;
}
}

View File

@@ -13,7 +13,9 @@ import dan200.computercraft.api.upgrades.UpgradeSerialiser;
import dan200.computercraft.impl.PocketUpgrades;
import dan200.computercraft.impl.TurtleUpgrades;
import dan200.computercraft.impl.UpgradeManager;
import dan200.computercraft.shared.network.MessageType;
import dan200.computercraft.shared.network.NetworkMessage;
import dan200.computercraft.shared.network.NetworkMessages;
import dan200.computercraft.shared.platform.PlatformHelper;
import net.minecraft.core.Registry;
import net.minecraft.network.FriendlyByteBuf;
@@ -27,7 +29,7 @@ import java.util.Objects;
/**
* Syncs turtle and pocket upgrades to the client.
*/
public class UpgradesLoadedMessage implements NetworkMessage<ClientNetworkContext> {
public final class UpgradesLoadedMessage implements NetworkMessage<ClientNetworkContext> {
private final Map<String, UpgradeManager.UpgradeWrapper<TurtleUpgradeSerialiser<?>, ITurtleUpgrade>> turtleUpgrades;
private final Map<String, UpgradeManager.UpgradeWrapper<PocketUpgradeSerialiser<?>, IPocketUpgrade>> pocketUpgrades;
@@ -65,7 +67,7 @@ public class UpgradesLoadedMessage implements NetworkMessage<ClientNetworkContex
}
@Override
public void toBytes(FriendlyByteBuf buf) {
public void write(FriendlyByteBuf buf) {
toBytes(buf, TurtleUpgradeSerialiser.registryId(), turtleUpgrades);
toBytes(buf, PocketUpgradeSerialiser.registryId(), pocketUpgrades);
}
@@ -95,4 +97,9 @@ public class UpgradesLoadedMessage implements NetworkMessage<ClientNetworkContex
TurtleUpgrades.instance().loadFromNetwork(turtleUpgrades);
PocketUpgrades.instance().loadFromNetwork(pocketUpgrades);
}
@Override
public MessageType<UpgradesLoadedMessage> type() {
return NetworkMessages.UPGRADES_LOADED;
}
}

View File

@@ -6,7 +6,9 @@ package dan200.computercraft.shared.network.client;
import dan200.computercraft.core.util.Nullability;
import dan200.computercraft.shared.computer.upload.UploadResult;
import dan200.computercraft.shared.network.MessageType;
import dan200.computercraft.shared.network.NetworkMessage;
import dan200.computercraft.shared.network.NetworkMessages;
import net.minecraft.network.FriendlyByteBuf;
import net.minecraft.network.chat.Component;
import net.minecraft.world.inventory.AbstractContainerMenu;
@@ -43,7 +45,7 @@ public class UploadResultMessage implements NetworkMessage<ClientNetworkContext>
}
@Override
public void toBytes(FriendlyByteBuf buf) {
public void write(FriendlyByteBuf buf) {
buf.writeVarInt(containerId);
buf.writeEnum(result);
if (result == UploadResult.ERROR) buf.writeComponent(Nullability.assertNonNull(errorMessage));
@@ -53,4 +55,9 @@ public class UploadResultMessage implements NetworkMessage<ClientNetworkContext>
public void handle(ClientNetworkContext context) {
context.handleUploadResult(containerId, result, errorMessage);
}
@Override
public MessageType<UploadResultMessage> type() {
return NetworkMessages.UPLOAD_RESULT;
}
}

View File

@@ -5,6 +5,8 @@
package dan200.computercraft.shared.network.server;
import dan200.computercraft.shared.computer.menu.ComputerMenu;
import dan200.computercraft.shared.network.MessageType;
import dan200.computercraft.shared.network.NetworkMessages;
import net.minecraft.network.FriendlyByteBuf;
import net.minecraft.world.inventory.AbstractContainerMenu;
@@ -23,8 +25,8 @@ public class ComputerActionServerMessage extends ComputerServerMessage {
}
@Override
public void toBytes(FriendlyByteBuf buf) {
super.toBytes(buf);
public void write(FriendlyByteBuf buf) {
super.write(buf);
buf.writeEnum(action);
}
@@ -37,6 +39,11 @@ public class ComputerActionServerMessage extends ComputerServerMessage {
}
}
@Override
public MessageType<ComputerActionServerMessage> type() {
return NetworkMessages.COMPUTER_ACTION;
}
public enum Action {
TURN_ON,
SHUTDOWN,

View File

@@ -28,7 +28,7 @@ public abstract class ComputerServerMessage implements NetworkMessage<ServerNetw
@Override
@OverridingMethodsMustInvokeSuper
public void toBytes(FriendlyByteBuf buf) {
public void write(FriendlyByteBuf buf) {
buf.writeVarInt(containerId);
}

View File

@@ -5,6 +5,8 @@
package dan200.computercraft.shared.network.server;
import dan200.computercraft.shared.computer.menu.ComputerMenu;
import dan200.computercraft.shared.network.MessageType;
import dan200.computercraft.shared.network.NetworkMessages;
import net.minecraft.network.FriendlyByteBuf;
import net.minecraft.world.inventory.AbstractContainerMenu;
@@ -30,8 +32,8 @@ public class KeyEventServerMessage extends ComputerServerMessage {
}
@Override
public void toBytes(FriendlyByteBuf buf) {
super.toBytes(buf);
public void write(FriendlyByteBuf buf) {
super.write(buf);
buf.writeByte(type);
buf.writeVarInt(key);
}
@@ -45,4 +47,9 @@ public class KeyEventServerMessage extends ComputerServerMessage {
input.keyDown(key, type == TYPE_REPEAT);
}
}
@Override
public MessageType<KeyEventServerMessage> type() {
return NetworkMessages.KEY_EVENT;
}
}

View File

@@ -5,6 +5,8 @@
package dan200.computercraft.shared.network.server;
import dan200.computercraft.shared.computer.menu.ComputerMenu;
import dan200.computercraft.shared.network.MessageType;
import dan200.computercraft.shared.network.NetworkMessages;
import net.minecraft.network.FriendlyByteBuf;
import net.minecraft.world.inventory.AbstractContainerMenu;
@@ -37,8 +39,8 @@ public class MouseEventServerMessage extends ComputerServerMessage {
}
@Override
public void toBytes(FriendlyByteBuf buf) {
super.toBytes(buf);
public void write(FriendlyByteBuf buf) {
super.write(buf);
buf.writeByte(type);
buf.writeVarInt(arg);
buf.writeVarInt(x);
@@ -55,4 +57,9 @@ public class MouseEventServerMessage extends ComputerServerMessage {
case TYPE_SCROLL -> input.mouseScroll(arg, x, y);
}
}
@Override
public MessageType<MouseEventServerMessage> type() {
return NetworkMessages.MOUSE_EVENT;
}
}

View File

@@ -7,6 +7,8 @@ package dan200.computercraft.shared.network.server;
import dan200.computercraft.shared.computer.core.ServerComputer;
import dan200.computercraft.shared.computer.menu.ComputerMenu;
import dan200.computercraft.shared.computer.menu.ServerInputHandler;
import dan200.computercraft.shared.network.MessageType;
import dan200.computercraft.shared.network.NetworkMessages;
import dan200.computercraft.shared.util.NBTUtil;
import net.minecraft.network.FriendlyByteBuf;
import net.minecraft.world.inventory.AbstractContainerMenu;
@@ -37,8 +39,8 @@ public class QueueEventServerMessage extends ComputerServerMessage {
}
@Override
public void toBytes(FriendlyByteBuf buf) {
super.toBytes(buf);
public void write(FriendlyByteBuf buf) {
super.write(buf);
buf.writeUtf(event);
buf.writeNbt(args == null ? null : NBTUtil.encodeObjects(args));
}
@@ -47,4 +49,9 @@ public class QueueEventServerMessage extends ComputerServerMessage {
protected void handle(ServerNetworkContext context, ComputerMenu container) {
container.getInput().queueEvent(event, args);
}
@Override
public MessageType<QueueEventServerMessage> type() {
return NetworkMessages.QUEUE_EVENT;
}
}

View File

@@ -9,6 +9,8 @@ import dan200.computercraft.shared.computer.menu.ComputerMenu;
import dan200.computercraft.shared.computer.upload.FileSlice;
import dan200.computercraft.shared.computer.upload.FileUpload;
import dan200.computercraft.shared.config.Config;
import dan200.computercraft.shared.network.MessageType;
import dan200.computercraft.shared.network.NetworkMessages;
import io.netty.handler.codec.DecoderException;
import net.minecraft.network.FriendlyByteBuf;
import net.minecraft.world.inventory.AbstractContainerMenu;
@@ -91,8 +93,8 @@ public class UploadFileMessage extends ComputerServerMessage {
}
@Override
public void toBytes(FriendlyByteBuf buf) {
super.toBytes(buf);
public void write(FriendlyByteBuf buf) {
super.write(buf);
buf.writeUUID(uuid);
buf.writeByte(flag);
@@ -166,4 +168,9 @@ public class UploadFileMessage extends ComputerServerMessage {
input.continueUpload(uuid, slices);
if ((flag & FLAG_LAST) != 0) input.finishUpload(player, uuid);
}
@Override
public MessageType<UploadFileMessage> type() {
return NetworkMessages.UPLOAD_FILE;
}
}

View File

@@ -10,6 +10,7 @@ import com.mojang.brigadier.arguments.ArgumentType;
import dan200.computercraft.api.network.wired.WiredElement;
import dan200.computercraft.api.peripheral.IPeripheral;
import dan200.computercraft.shared.config.ConfigFile;
import dan200.computercraft.shared.network.MessageType;
import dan200.computercraft.shared.network.NetworkMessage;
import dan200.computercraft.shared.network.client.ClientNetworkContext;
import dan200.computercraft.shared.network.container.ContainerData;
@@ -164,6 +165,18 @@ public interface PlatformHelper extends dan200.computercraft.impl.PlatformHelper
*/
void openMenu(Player player, MenuProvider owner, ContainerData menu);
/**
* Create a new {@link MessageType}.
*
* @param id The descriminator for this message type.
* @param channel The channel name for this message type.
* @param klass The type of this message.
* @param reader The function which reads the packet from a buffer. Should be the inverse to {@link NetworkMessage#write(FriendlyByteBuf)}.
* @param <T> The type of this message.
* @return The new {@link MessageType} instance.
*/
<T extends NetworkMessage<?>> MessageType<T> createMessageType(int id, ResourceLocation channel, Class<T> klass, FriendlyByteBuf.Reader<T> reader);
/**
* Send a message to a specific player.
*

View File

@@ -12,7 +12,6 @@ import dan200.computercraft.api.pocket.IPocketUpgrade;
import dan200.computercraft.api.upgrades.UpgradeData;
import dan200.computercraft.core.computer.ComputerSide;
import dan200.computercraft.impl.PocketUpgrades;
import dan200.computercraft.shared.ModRegistry;
import dan200.computercraft.shared.common.IColouredItem;
import dan200.computercraft.shared.computer.core.ComputerFamily;
import dan200.computercraft.shared.computer.core.ServerContext;
@@ -60,14 +59,6 @@ public class PocketComputerItem extends Item implements IComputerItem, IMedia, I
this.family = family;
}
public static ItemStack create(int id, @Nullable String label, int colour, ComputerFamily family, @Nullable UpgradeData<IPocketUpgrade> upgrade) {
return switch (family) {
case NORMAL -> ModRegistry.Items.POCKET_COMPUTER_NORMAL.get().create(id, label, colour, upgrade);
case ADVANCED -> ModRegistry.Items.POCKET_COMPUTER_ADVANCED.get().create(id, label, colour, upgrade);
default -> ItemStack.EMPTY;
};
}
public ItemStack create(int id, @Nullable String label, int colour, @Nullable UpgradeData<IPocketUpgrade> upgrade) {
var result = new ItemStack(this);
if (id >= 0) result.getOrCreateTag().putInt(NBT_ID, id);
@@ -243,17 +234,16 @@ public class PocketComputerItem extends Item implements IComputerItem, IMedia, I
return IComputerItem.super.getLabel(stack);
}
@Override
public ComputerFamily getFamily() {
return family;
}
@Override
public ItemStack withFamily(ItemStack stack, ComputerFamily family) {
return create(
public ItemStack changeItem(ItemStack stack, Item newItem) {
return newItem instanceof PocketComputerItem pocket ? pocket.create(
getComputerID(stack), getLabel(stack), getColour(stack),
family, getUpgradeWithData(stack)
);
getUpgradeWithData(stack)
) : ItemStack.EMPTY;
}
// IMedia

View File

@@ -81,7 +81,6 @@ public final class PocketComputerUpgradeRecipe extends CustomRecipe {
if (upgrade == null) return ItemStack.EMPTY;
// Construct the new stack
var family = itemComputer.getFamily();
var computerID = itemComputer.getComputerID(computer);
var label = itemComputer.getLabel(computer);
var colour = itemComputer.getColour(computer);

View File

@@ -10,7 +10,6 @@ import dan200.computercraft.api.turtle.TurtleSide;
import dan200.computercraft.api.upgrades.UpgradeData;
import dan200.computercraft.shared.computer.blocks.AbstractComputerBlock;
import dan200.computercraft.shared.computer.blocks.AbstractComputerBlockEntity;
import dan200.computercraft.shared.computer.core.ComputerFamily;
import dan200.computercraft.shared.platform.RegistryEntry;
import dan200.computercraft.shared.turtle.core.TurtleBrain;
import dan200.computercraft.shared.turtle.items.TurtleItem;
@@ -52,6 +51,16 @@ import static dan200.computercraft.shared.util.WaterloggableHelpers.getFluidStat
public class TurtleBlock extends AbstractComputerBlock<TurtleBlockEntity> implements SimpleWaterloggedBlock {
public static final DirectionProperty FACING = BlockStateProperties.HORIZONTAL_FACING;
/**
* The explosion resistance to use when a turtle is "immune" to explosions.
* <p>
* This is used as the default explosion resistance for advanced turtles, and the resistance for entity-based
* explosions (e.g. creepers).
*
* @see #getExplosionResistance(BlockState, BlockGetter, BlockPos, Explosion)
*/
public static final float IMMUNE_EXPLOSION_RESISTANCE = 2000f;
private static final VoxelShape DEFAULT_SHAPE = Shapes.box(
0.125, 0.125, 0.125,
0.875, 0.875, 0.875
@@ -59,8 +68,8 @@ public class TurtleBlock extends AbstractComputerBlock<TurtleBlockEntity> implem
private final BlockEntityTicker<TurtleBlockEntity> clientTicker = (level, pos, state, computer) -> computer.clientTick();
public TurtleBlock(Properties settings, ComputerFamily family, RegistryEntry<BlockEntityType<TurtleBlockEntity>> type) {
super(settings, family, type);
public TurtleBlock(Properties settings, RegistryEntry<BlockEntityType<TurtleBlockEntity>> type) {
super(settings, type);
registerDefaultState(getStateDefinition().any()
.setValue(FACING, Direction.NORTH)
.setValue(WATERLOGGED, false)
@@ -149,20 +158,21 @@ public class TurtleBlock extends AbstractComputerBlock<TurtleBlockEntity> implem
@ForgeOverride
public float getExplosionResistance(BlockState state, BlockGetter world, BlockPos pos, Explosion explosion) {
var exploder = explosion.getDirectSourceEntity();
if (getFamily() == ComputerFamily.ADVANCED || exploder instanceof LivingEntity || exploder instanceof AbstractHurtingProjectile) {
return 2000;
if (exploder instanceof LivingEntity || exploder instanceof AbstractHurtingProjectile) {
return IMMUNE_EXPLOSION_RESISTANCE;
}
return explosionResistance;
return getExplosionResistance();
}
@Override
protected ItemStack getItem(AbstractComputerBlockEntity tile) {
if (!(tile instanceof TurtleBlockEntity turtle)) return ItemStack.EMPTY;
if (!(asItem() instanceof TurtleItem item)) return ItemStack.EMPTY;
var access = turtle.getAccess();
return TurtleItem.create(
turtle.getComputerID(), turtle.getLabel(), access.getColour(), turtle.getFamily(),
return item.create(
turtle.getComputerID(), turtle.getLabel(), access.getColour(),
withPersistedData(access.getUpgradeWithData(TurtleSide.LEFT)),
withPersistedData(access.getUpgradeWithData(TurtleSide.RIGHT)),
access.getFuelLevel(), turtle.getOverlay()

View File

@@ -38,6 +38,7 @@ import net.minecraft.world.phys.Vec3;
import javax.annotation.Nullable;
import java.util.Collections;
import java.util.function.IntSupplier;
public class TurtleBlockEntity extends AbstractComputerBlockEntity implements BasicContainer {
public static final int INVENTORY_SIZE = 16;
@@ -53,13 +54,17 @@ public class TurtleBlockEntity extends AbstractComputerBlockEntity implements Ba
private final NonNullList<ItemStack> inventory = NonNullList.withSize(INVENTORY_SIZE, ItemStack.EMPTY);
private final NonNullList<ItemStack> inventorySnapshot = NonNullList.withSize(INVENTORY_SIZE, ItemStack.EMPTY);
private boolean inventoryChanged = false;
private final IntSupplier fuelLimit;
private TurtleBrain brain = new TurtleBrain(this);
private MoveState moveState = MoveState.NOT_MOVED;
private @Nullable IPeripheral peripheral;
private @Nullable Runnable onMoved;
public TurtleBlockEntity(BlockEntityType<? extends TurtleBlockEntity> type, BlockPos pos, BlockState state, ComputerFamily family) {
public TurtleBlockEntity(BlockEntityType<? extends TurtleBlockEntity> type, BlockPos pos, BlockState state, IntSupplier fuelLimit, ComputerFamily family) {
super(type, pos, state, family);
this.fuelLimit = fuelLimit;
}
boolean hasMoved() {
@@ -172,8 +177,6 @@ public class TurtleBlockEntity extends AbstractComputerBlockEntity implements Ba
return hasPeripheralUpgradeOnSide(localSide);
}
// IDirectionalTile
@Override
public Direction getDirection() {
return getBlockState().getValue(TurtleBlock.FACING);
@@ -272,6 +275,10 @@ public class TurtleBlockEntity extends AbstractComputerBlockEntity implements Ba
// Privates
public int getFuelLimit() {
return fuelLimit.getAsInt();
}
private boolean hasPeripheralUpgradeOnSide(ComputerSide side) {
ITurtleUpgrade upgrade;
switch (side) {

View File

@@ -395,11 +395,7 @@ public class TurtleBrain implements TurtleAccessInternal {
@Override
public int getFuelLimit() {
if (owner.getFamily() == ComputerFamily.ADVANCED) {
return Config.advancedTurtleFuelLimit;
} else {
return Config.turtleFuelLimit;
}
return owner.getFuelLimit();
}
@Override

View File

@@ -10,9 +10,7 @@ import dan200.computercraft.api.turtle.ITurtleUpgrade;
import dan200.computercraft.api.turtle.TurtleSide;
import dan200.computercraft.api.upgrades.UpgradeData;
import dan200.computercraft.impl.TurtleUpgrades;
import dan200.computercraft.shared.ModRegistry;
import dan200.computercraft.shared.common.IColouredItem;
import dan200.computercraft.shared.computer.core.ComputerFamily;
import dan200.computercraft.shared.computer.items.AbstractComputerItem;
import dan200.computercraft.shared.turtle.blocks.TurtleBlock;
import dan200.computercraft.shared.util.NBTUtil;
@@ -20,6 +18,7 @@ import net.minecraft.core.cauldron.CauldronInteraction;
import net.minecraft.network.chat.Component;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.world.InteractionResult;
import net.minecraft.world.item.Item;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.level.block.LayeredCauldronBlock;
@@ -32,20 +31,6 @@ public class TurtleItem extends AbstractComputerItem implements IColouredItem {
super(block, settings);
}
public static ItemStack create(
int id, @Nullable String label, int colour, ComputerFamily family,
@Nullable UpgradeData<ITurtleUpgrade> leftUpgrade, @Nullable UpgradeData<ITurtleUpgrade> rightUpgrade,
int fuelLevel, @Nullable ResourceLocation overlay
) {
return switch (family) {
case NORMAL ->
ModRegistry.Items.TURTLE_NORMAL.get().create(id, label, colour, leftUpgrade, rightUpgrade, fuelLevel, overlay);
case ADVANCED ->
ModRegistry.Items.TURTLE_ADVANCED.get().create(id, label, colour, leftUpgrade, rightUpgrade, fuelLevel, overlay);
default -> ItemStack.EMPTY;
};
}
public ItemStack create(
int id, @Nullable String label, int colour,
@Nullable UpgradeData<ITurtleUpgrade> leftUpgrade, @Nullable UpgradeData<ITurtleUpgrade> rightUpgrade,
@@ -119,13 +104,13 @@ public class TurtleItem extends AbstractComputerItem implements IColouredItem {
}
@Override
public ItemStack withFamily(ItemStack stack, ComputerFamily family) {
return create(
public ItemStack changeItem(ItemStack stack, Item newItem) {
return newItem instanceof TurtleItem turtle ? turtle.create(
getComputerID(stack), getLabel(stack),
getColour(stack), family,
getColour(stack),
getUpgradeWithData(stack, TurtleSide.LEFT), getUpgradeWithData(stack, TurtleSide.RIGHT),
getFuelLevel(stack), getOverlay(stack)
);
) : ItemStack.EMPTY;
}
public @Nullable ITurtleUpgrade getUpgrade(ItemStack stack, TurtleSide side) {

View File

@@ -58,7 +58,7 @@ public class TurtleOverlayRecipe extends CustomShapelessRecipe {
return ModRegistry.RecipeSerializers.TURTLE_OVERLAY.get();
}
public static class Serializer implements RecipeSerializer<TurtleOverlayRecipe> {
public static class Serialiser implements RecipeSerializer<TurtleOverlayRecipe> {
@Override
public TurtleOverlayRecipe fromJson(ResourceLocation id, JsonObject json) {
var recipe = ShapelessRecipeSpec.fromJson(json);

View File

@@ -10,6 +10,7 @@ accessWidener v1 named
accessible method net/minecraft/client/renderer/item/ItemProperties register (Lnet/minecraft/world/item/Item;Lnet/minecraft/resources/ResourceLocation;Lnet/minecraft/client/renderer/item/ClampedItemPropertyFunction;)V
accessible method net/minecraft/client/renderer/blockentity/BlockEntityRenderers register (Lnet/minecraft/world/level/block/entity/BlockEntityType;Lnet/minecraft/client/renderer/blockentity/BlockEntityRendererProvider;)V
accessible class net/minecraft/world/item/CreativeModeTab$Output
accessible field net/minecraft/world/item/CreativeModeTabs OP_BLOCKS Lnet/minecraft/resources/ResourceKey;
# Containers
accessible class net/minecraft/world/inventory/MenuType$MenuSupplier

View File

@@ -13,6 +13,7 @@ import dan200.computercraft.api.peripheral.IPeripheral;
import dan200.computercraft.impl.AbstractComputerCraftAPI;
import dan200.computercraft.impl.ComputerCraftAPIService;
import dan200.computercraft.shared.config.ConfigFile;
import dan200.computercraft.shared.network.MessageType;
import dan200.computercraft.shared.network.NetworkMessage;
import dan200.computercraft.shared.network.client.ClientNetworkContext;
import dan200.computercraft.shared.network.container.ContainerData;
@@ -168,6 +169,13 @@ public class TestPlatformHelper extends AbstractComputerCraftAPI implements Plat
throw new UnsupportedOperationException("Cannot open menu inside tests");
}
@Override
public <T extends NetworkMessage<?>> MessageType<T> createMessageType(int id, ResourceLocation channel, Class<T> klass, FriendlyByteBuf.Reader<T> reader) {
record TypeImpl<T extends NetworkMessage<?>>(Function<FriendlyByteBuf, T> reader) implements MessageType<T> {
}
return new TypeImpl<>(reader);
}
@Override
public ComponentAccess<IPeripheral> createPeripheralAccess(Consumer<Direction> invalidate) {
throw new UnsupportedOperationException("Cannot interact with the world inside tests");

View File

@@ -29,7 +29,7 @@ class PlayRecordClientMessageTest {
@Property
public void testRoundTrip(@ForAll("message") PlayRecordClientMessage message) {
var buffer = new FriendlyByteBuf(Unpooled.directBuffer());
message.toBytes(buffer);
message.write(buffer);
var converted = new PlayRecordClientMessage(buffer);
assertEquals(buffer.readableBytes(), 0, "Whole packet was read");

View File

@@ -63,7 +63,7 @@ public class UploadFileMessageTest {
private static List<UploadFileMessage> roundtripPackets(List<UploadFileMessage> packets) {
return packets.stream().map(packet -> {
var buffer = new FriendlyByteBuf(Unpooled.directBuffer());
packet.toBytes(buffer);
packet.write(buffer);
// We include things like file size in the packet, but not in the count, so grant a slightly larger threshold.
assertThat("Packet is too large", buffer.writerIndex(), lessThanOrEqualTo(MAX_PACKET_SIZE + 128));
if ((packet.flag & FLAG_LAST) == 0) {

View File

@@ -5,14 +5,13 @@
package dan200.computercraft.core.apis.http.websocket;
import com.google.common.base.Strings;
import dan200.computercraft.api.lua.LuaException;
import dan200.computercraft.core.Logging;
import dan200.computercraft.core.apis.IAPIEnvironment;
import dan200.computercraft.core.apis.http.HTTPRequestException;
import dan200.computercraft.core.apis.http.NetworkUtils;
import dan200.computercraft.core.apis.http.Resource;
import dan200.computercraft.core.apis.http.ResourceGroup;
import dan200.computercraft.core.apis.http.*;
import dan200.computercraft.core.apis.http.options.Options;
import dan200.computercraft.core.metrics.Metrics;
import dan200.computercraft.core.util.AtomicHelpers;
import io.netty.bootstrap.Bootstrap;
import io.netty.buffer.Unpooled;
import io.netty.channel.Channel;
@@ -24,10 +23,8 @@ import io.netty.handler.codec.http.HttpClientCodec;
import io.netty.handler.codec.http.HttpHeaderNames;
import io.netty.handler.codec.http.HttpHeaders;
import io.netty.handler.codec.http.HttpObjectAggregator;
import io.netty.handler.codec.http.websocketx.BinaryWebSocketFrame;
import io.netty.handler.codec.http.websocketx.TextWebSocketFrame;
import io.netty.handler.codec.http.websocketx.WebSocketClientProtocolHandler;
import io.netty.handler.codec.http.websocketx.WebSocketVersion;
import io.netty.handler.codec.http.websocketx.*;
import io.netty.util.concurrent.GenericFutureListener;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -35,6 +32,7 @@ import javax.annotation.Nullable;
import java.net.URI;
import java.nio.ByteBuffer;
import java.util.concurrent.Future;
import java.util.concurrent.atomic.AtomicInteger;
/**
* Provides functionality to verify and connect to a remote websocket.
@@ -57,6 +55,9 @@ public class Websocket extends Resource<Websocket> implements WebsocketClient {
private final HttpHeaders headers;
private final int timeout;
private final AtomicInteger inFlight = new AtomicInteger(0);
private final GenericFutureListener<? extends io.netty.util.concurrent.Future<? super Void>> onSend = f -> inFlight.decrementAndGet();
public Websocket(ResourceGroup<Websocket> limiter, IAPIEnvironment environment, URI uri, String address, HttpHeaders headers, int timeout) {
super(limiter);
this.environment = environment;
@@ -170,18 +171,27 @@ public class Websocket extends Resource<Websocket> implements WebsocketClient {
}
@Override
public void sendText(String message) {
environment.observe(Metrics.WEBSOCKET_OUTGOING, message.length());
var channel = channel();
if (channel != null) channel.writeAndFlush(new TextWebSocketFrame(message));
public void sendText(String message) throws LuaException {
sendMessage(new TextWebSocketFrame(message), message.length());
}
@Override
public void sendBinary(ByteBuffer message) {
environment.observe(Metrics.WEBSOCKET_OUTGOING, message.remaining());
public void sendBinary(ByteBuffer message) throws LuaException {
long size = message.remaining();
sendMessage(new BinaryWebSocketFrame(Unpooled.wrappedBuffer(message)), size);
}
private void sendMessage(WebSocketFrame frame, long size) throws LuaException {
var channel = channel();
if (channel != null) channel.writeAndFlush(new BinaryWebSocketFrame(Unpooled.wrappedBuffer(message)));
if (channel == null) return;
// Grow the number of in-flight requests, aborting if we've hit the limit. This is then decremented when the
// promise finishes.
if (!AtomicHelpers.incrementToLimit(inFlight, ResourceQueue.DEFAULT_LIMIT)) {
throw new LuaException("Too many ongoing websocket messages");
}
environment.observe(Metrics.WEBSOCKET_OUTGOING, size);
channel.writeAndFlush(frame).addListener(onSend);
}
}

View File

@@ -4,6 +4,7 @@
package dan200.computercraft.core.apis.http.websocket;
import dan200.computercraft.api.lua.LuaException;
import dan200.computercraft.core.apis.http.HTTPRequestException;
import java.io.Closeable;
@@ -39,15 +40,17 @@ public interface WebsocketClient extends Closeable {
* Send a text websocket frame.
*
* @param message The message to send.
* @throws LuaException If the message could not be sent.
*/
void sendText(String message);
void sendText(String message) throws LuaException;
/**
* Send a binary websocket frame.
*
* @param message The message to send.
* @throws LuaException If the message could not be sent.
*/
void sendBinary(ByteBuffer message);
void sendBinary(ByteBuffer message) throws LuaException;
/**
* Parse an address, ensuring it is a valid websocket URI.

View File

@@ -0,0 +1,29 @@
// SPDX-FileCopyrightText: 2023 The CC: Tweaked Developers
//
// SPDX-License-Identifier: MPL-2.0
package dan200.computercraft.core.util;
import java.util.concurrent.atomic.AtomicInteger;
public final class AtomicHelpers {
private AtomicHelpers() {
}
/**
* A version of {@link AtomicInteger#getAndIncrement()}, which increments until a limit is reached.
*
* @param atomic The atomic to increment.
* @param limit The maximum value of {@code value}.
* @return Whether the value was sucessfully incremented.
*/
public static boolean incrementToLimit(AtomicInteger atomic, int limit) {
int value;
do {
value = atomic.get();
if (value >= limit) return false;
} while (!atomic.compareAndSet(value, value + 1));
return true;
}
}

View File

@@ -1,3 +1,20 @@
# New features in CC: Tweaked 1.109.3
* Command computers now display in the operator items creative tab.
Several bug fixes:
* Error if too many websocket messages are queued to be sent at once.
* Fix trailing-comma on method calls (e.g. `x:f(a, )` not using our custom error message.
* Fix internal compiler error when using `goto` as the first statement in an `if` block.
* Fix incorrect incorrect resizing of a tables' hash part when adding and removing keys.
# New features in CC: Tweaked 1.109.2
* `math.random` now uses Lua 5.4's random number generator.
Several bug fixes:
* Fix errors involving `goto` statements having the wrong line number.
# New features in CC: Tweaked 1.109.1
Several bug fixes:

View File

@@ -1,9 +1,11 @@
New features in CC: Tweaked 1.109.1
New features in CC: Tweaked 1.109.3
* Command computers now display in the operator items creative tab.
Several bug fixes:
* Fix `mouse_drag` event not firing for right and middle mouse buttons.
* Fix crash when syntax errors involve `goto` or `::`.
* Fix deadlock occuring when adding/removing observers.
* Allow placing seeds into compostor barrels with `turtle.place()`.
* Error if too many websocket messages are queued to be sent at once.
* Fix trailing-comma on method calls (e.g. `x:f(a, )` not using our custom error message.
* Fix internal compiler error when using `goto` as the first statement in an `if` block.
* Fix incorrect incorrect resizing of a tables' hash part when adding and removing keys.
Type "help changelog" to see the full version history.

View File

@@ -89,6 +89,30 @@ class TestHttpApi {
}
}
@Test
fun `Errors if too many websocket messages are sent`() {
runServer {
LuaTaskRunner.runTest {
val httpApi = addApi(HTTPAPI(environment))
assertThat("http.websocket succeeded", httpApi.websocket(ObjectArguments(WS_URL)), array(equalTo(true)))
val connectEvent = pullEvent()
assertThat(connectEvent, array(equalTo("websocket_success"), equalTo(WS_URL), isA(WebsocketHandle::class.java)))
val websocket = connectEvent[2] as WebsocketHandle
val error = assertThrows<LuaException> {
for (i in 0 until 10_000) {
websocket.send(Coerced(LuaValues.encode("Hello")), Optional.of(false))
}
}
websocket.close()
assertThat(error.message, equalTo("Too many ongoing websocket messages"))
}
}
}
@Test
fun `Queues an event when the socket is externally closed`() {
runServer { stop ->

View File

@@ -478,6 +478,7 @@ Unexpected ) in function call.
1 | f(2, )
| ^ Tip: Try removing this ,.
```
```lua
f(2, 3, )
```
@@ -491,3 +492,17 @@ Unexpected ) in function call.
1 | f(2, 3, )
| ^ Tip: Try removing this ,.
```
```lua
x:f(2, 3, )
```
```txt
Unexpected ) in function call.
|
1 | x:f(2, 3, )
| ^
|
1 | x:f(2, 3, )
| ^ Tip: Try removing this ,.
```

View File

@@ -48,7 +48,7 @@ addRemappedConfiguration("testWithIris")
dependencies {
clientCompileOnly(variantOf(libs.emi) { classifier("api") })
modImplementation(libs.bundles.externalMods.fabric)
modImplementation(libs.bundles.externalMods.fabric) { cct.exclude(this) }
modCompileOnly(libs.bundles.externalMods.fabric.compile) {
exclude("net.fabricmc", "fabric-loader")
exclude("net.fabricmc.fabric-api")
@@ -72,9 +72,9 @@ dependencies {
include(libs.nightConfig.toml)
// Pull in our other projects. See comments in MinecraftConfigurations on this nastiness.
api(commonClasses(project(":fabric-api")))
clientApi(clientClasses(project(":fabric-api")))
implementation(project(":core"))
api(commonClasses(project(":fabric-api"))) { cct.exclude(this) }
clientApi(clientClasses(project(":fabric-api"))) { cct.exclude(this) }
implementation(project(":core")) { cct.exclude(this) }
// These are transitive deps of :core, so we don't need these deps. However, we want them to appear as runtime deps
// in our POM, and this is the easiest way.
runtimeOnly(libs.cobalt)
@@ -168,7 +168,11 @@ loom {
configureForGameTest(this)
property("fabric-api.gametest")
property("fabric-api.gametest.report-file", layout.buildDirectory.dir("test-results/runGametest.xml").getAbsolutePath())
property(
"fabric-api.gametest.report-file",
layout.buildDirectory.dir("test-results/runGametest.xml")
.getAbsolutePath(),
)
runDir("run/gametest")
}
}
@@ -258,9 +262,7 @@ publishing {
publications {
named("maven", MavenPublication::class) {
mavenDependencies {
exclude(dependencies.create("cc.tweaked:"))
exclude(libs.jei.fabric.get())
exclude(libs.modmenu.get())
cct.configureExcludes(this)
}
}
}

View File

@@ -8,10 +8,11 @@ import dan200.computercraft.api.ComputerCraftAPI;
import dan200.computercraft.client.model.CustomModelLoader;
import dan200.computercraft.shared.ModRegistry;
import dan200.computercraft.shared.config.ConfigSpec;
import dan200.computercraft.shared.network.NetworkMessages;
import dan200.computercraft.shared.network.client.ClientNetworkContext;
import dan200.computercraft.shared.peripheral.modem.wired.CableBlock;
import dan200.computercraft.shared.platform.FabricConfigFile;
import dan200.computercraft.shared.platform.NetworkHandler;
import dan200.computercraft.shared.platform.FabricMessageType;
import net.fabricmc.fabric.api.blockrenderlayer.v1.BlockRenderLayerMap;
import net.fabricmc.fabric.api.client.event.lifecycle.v1.ClientTickEvents;
import net.fabricmc.fabric.api.client.model.loading.v1.PreparableModelLoadingPlugin;
@@ -30,10 +31,11 @@ import static dan200.computercraft.core.util.Nullability.assertNonNull;
public class ComputerCraftClient {
public static void init() {
ClientPlayNetworking.registerGlobalReceiver(NetworkHandler.ID, (client, handler, buf, responseSender) -> {
var packet = NetworkHandler.decodeClient(buf);
if (packet != null) client.execute(() -> packet.handle(ClientNetworkContext.get()));
});
for (var type : NetworkMessages.getClientbound()) {
ClientPlayNetworking.registerGlobalReceiver(
FabricMessageType.toFabricType(type), (packet, player, responseSender) -> packet.payload().handle(ClientNetworkContext.get())
);
}
ClientRegistry.register();
ClientRegistry.registerItemColours(ColorProviderRegistry.ITEM::register);

View File

@@ -10,9 +10,9 @@ import dan200.computercraft.client.model.FoiledModel;
import dan200.computercraft.client.render.ModelRenderer;
import dan200.computercraft.shared.network.NetworkMessage;
import dan200.computercraft.shared.network.server.ServerNetworkContext;
import dan200.computercraft.shared.platform.NetworkHandler;
import dan200.computercraft.shared.platform.FabricMessageType;
import net.fabricmc.fabric.api.client.networking.v1.ClientPlayNetworking;
import net.fabricmc.fabric.api.renderer.v1.model.ModelHelper;
import net.minecraft.client.Minecraft;
import net.minecraft.client.renderer.MultiBufferSource;
import net.minecraft.client.renderer.Sheets;
import net.minecraft.client.renderer.entity.ItemRenderer;
@@ -29,7 +29,7 @@ public class ClientPlatformHelperImpl implements ClientPlatformHelper {
@Override
public void sendToServer(NetworkMessage<ServerNetworkContext> message) {
Minecraft.getInstance().player.connection.send(NetworkHandler.encodeServer(message));
ClientPlayNetworking.send(FabricMessageType.toFabricPacket(message));
}
@Override

View File

@@ -1,7 +1,6 @@
{
"type": "computercraft:computer_upgrade",
"category": "redstone",
"family": "advanced",
"key": {"#": {"tag": "c:gold_ingots"}, "C": {"item": "computercraft:computer_normal"}},
"pattern": ["###", "#C#", "# #"],
"result": {"item": "computercraft:computer_advanced"},

View File

@@ -1,7 +1,6 @@
{
"type": "computercraft:computer_upgrade",
"category": "redstone",
"family": "advanced",
"key": {"#": {"tag": "c:gold_ingots"}, "C": {"item": "computercraft:pocket_computer_normal"}},
"pattern": ["###", "#C#", "# #"],
"result": {"item": "computercraft:pocket_computer_advanced"},

View File

@@ -1,7 +1,6 @@
{
"type": "computercraft:turtle",
"category": "redstone",
"family": "advanced",
"key": {
"#": {"tag": "c:gold_ingots"},
"C": {"item": "computercraft:computer_advanced"},

View File

@@ -1,7 +1,6 @@
{
"type": "computercraft:computer_upgrade",
"category": "redstone",
"family": "advanced",
"key": {
"#": {"tag": "c:gold_ingots"},
"B": {"item": "minecraft:gold_block"},

View File

@@ -1,7 +1,6 @@
{
"type": "computercraft:turtle",
"category": "redstone",
"family": "normal",
"key": {
"#": {"tag": "c:iron_ingots"},
"C": {"item": "computercraft:computer_normal"},

View File

@@ -0,0 +1 @@
{"replace": false, "values": ["computercraft:computer_command"]}

View File

@@ -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.network.NetworkMessages;
import dan200.computercraft.shared.network.client.UpgradesLoadedMessage;
import dan200.computercraft.shared.peripheral.commandblock.CommandBlockPeripheral;
import dan200.computercraft.shared.peripheral.generic.methods.InventoryMethods;
@@ -20,16 +21,19 @@ import dan200.computercraft.shared.peripheral.modem.wired.CableBlockEntity;
import dan200.computercraft.shared.peripheral.modem.wired.WiredModemFullBlockEntity;
import dan200.computercraft.shared.peripheral.modem.wireless.WirelessModemBlockEntity;
import dan200.computercraft.shared.platform.FabricConfigFile;
import dan200.computercraft.shared.platform.NetworkHandler;
import dan200.computercraft.shared.platform.FabricMessageType;
import dan200.computercraft.shared.platform.PlatformHelper;
import net.fabricmc.fabric.api.command.v2.CommandRegistrationCallback;
import net.fabricmc.fabric.api.event.lifecycle.v1.ServerLifecycleEvents;
import net.fabricmc.fabric.api.event.lifecycle.v1.ServerTickEvents;
import net.fabricmc.fabric.api.event.player.PlayerBlockBreakEvents;
import net.fabricmc.fabric.api.event.player.UseBlockCallback;
import net.fabricmc.fabric.api.itemgroup.v1.ItemGroupEvents;
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.minecraft.core.registries.BuiltInRegistries;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.server.packs.PackType;
import net.minecraft.server.packs.resources.PreparableReloadListener;
@@ -45,7 +49,12 @@ public class ComputerCraft {
private static final LevelResource SERVERCONFIG = new LevelResource("serverconfig");
public static void init() {
NetworkHandler.init();
for (var type : NetworkMessages.getServerbound()) {
ServerPlayNetworking.registerGlobalReceiver(
FabricMessageType.toFabricType(type), (packet, player, sender) -> packet.payload().handle(() -> player)
);
}
ModRegistry.register();
ModRegistry.registerMainThread();
@@ -96,6 +105,11 @@ public class ComputerCraft {
if (pool != null) tableBuilder.withPool(pool);
});
ItemGroupEvents.MODIFY_ENTRIES_ALL.register((tab, entries) -> CommonHooks.onBuildCreativeTab(
BuiltInRegistries.CREATIVE_MODE_TAB.getResourceKey(tab).orElseThrow(),
entries.getContext(), entries
));
CommonHooks.onDatapackReload((name, listener) -> ResourceManagerHelper.get(PackType.SERVER_DATA).registerReloadListener(new ReloadListener(name, listener)));
FabricDetailRegistries.FLUID_VARIANT.addProvider(FluidDetails::fill);

View File

@@ -0,0 +1,51 @@
// SPDX-FileCopyrightText: 2023 The CC: Tweaked Developers
//
// SPDX-License-Identifier: MPL-2.0
package dan200.computercraft.shared.platform;
import dan200.computercraft.shared.network.MessageType;
import dan200.computercraft.shared.network.NetworkMessage;
import net.fabricmc.fabric.api.networking.v1.FabricPacket;
import net.fabricmc.fabric.api.networking.v1.PacketType;
import net.minecraft.network.FriendlyByteBuf;
import net.minecraft.resources.ResourceLocation;
import java.util.function.Function;
/**
* An implementation of {@link MessageType} for Fabric.
* <p>
* This provides conversions between the {@link FabricPacket}/{@link PacketType} and {@link NetworkMessage}/{@link MessageType}
* interfaces, allowing us to interop between the two.
*
* @param type The underlying {@link PacketType}
* @param <T> The type of the message.
*/
public record FabricMessageType<T extends NetworkMessage<?>>(
PacketType<PacketWrapper<T>> type
) implements MessageType<T> {
public FabricMessageType(ResourceLocation id, Function<FriendlyByteBuf, T> reader) {
this(PacketType.create(id, b -> new PacketWrapper<>(reader.apply(b))));
}
public static <T extends NetworkMessage<?>> PacketType<PacketWrapper<T>> toFabricType(MessageType<T> type) {
return ((FabricMessageType<T>) type).type();
}
public static FabricPacket toFabricPacket(NetworkMessage<?> message) {
return new PacketWrapper<>(message);
}
public record PacketWrapper<T extends NetworkMessage<?>>(T payload) implements FabricPacket {
@Override
public void write(FriendlyByteBuf buf) {
payload().write(buf);
}
@Override
public PacketType<?> getType() {
return FabricMessageType.toFabricType(payload().type());
}
}
}

View File

@@ -1,94 +0,0 @@
// SPDX-FileCopyrightText: 2022 The CC: Tweaked Developers
//
// SPDX-License-Identifier: MPL-2.0
package dan200.computercraft.shared.platform;
import dan200.computercraft.api.ComputerCraftAPI;
import dan200.computercraft.shared.network.NetworkMessage;
import dan200.computercraft.shared.network.NetworkMessages;
import dan200.computercraft.shared.network.client.ClientNetworkContext;
import dan200.computercraft.shared.network.server.ServerNetworkContext;
import io.netty.buffer.Unpooled;
import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
import it.unimi.dsi.fastutil.objects.Object2IntMap;
import it.unimi.dsi.fastutil.objects.Object2IntOpenHashMap;
import net.fabricmc.fabric.api.networking.v1.ServerPlayNetworking;
import net.minecraft.network.FriendlyByteBuf;
import net.minecraft.network.protocol.game.ClientboundCustomPayloadPacket;
import net.minecraft.network.protocol.game.ServerboundCustomPayloadPacket;
import net.minecraft.resources.ResourceLocation;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.annotation.Nullable;
import java.util.function.Function;
public final class NetworkHandler {
private static final Logger LOGGER = LoggerFactory.getLogger(NetworkHandler.class);
public static final ResourceLocation ID = new ResourceLocation(ComputerCraftAPI.MOD_ID, "main");
private static final Int2ObjectMap<Function<FriendlyByteBuf, ? extends NetworkMessage<ClientNetworkContext>>> clientPackets = new Int2ObjectOpenHashMap<>();
private static final Int2ObjectMap<Function<FriendlyByteBuf, ? extends NetworkMessage<ServerNetworkContext>>> serverPackets = new Int2ObjectOpenHashMap<>();
private static final Object2IntMap<Class<? extends NetworkMessage<?>>> packetIds = new Object2IntOpenHashMap<>();
public static void init() {
ServerPlayNetworking.registerGlobalReceiver(ID, (server, player, handler, buf, responseSender) -> {
var packet = decodeServer(buf);
if (packet != null) server.execute(() -> packet.handle(handler::getPlayer));
});
NetworkMessages.register(new NetworkMessages.PacketRegistry() {
@Override
public <T extends NetworkMessage<ClientNetworkContext>> void registerClientbound(int id, Class<T> type, Function<FriendlyByteBuf, T> decoder) {
clientPackets.put(id, decoder);
packetIds.put(type, id);
}
@Override
public <T extends NetworkMessage<ServerNetworkContext>> void registerServerbound(int id, Class<T> type, Function<FriendlyByteBuf, T> decoder) {
serverPackets.put(id, decoder);
packetIds.put(type, id);
}
});
}
private static FriendlyByteBuf encode(NetworkMessage<?> message) {
var buf = new FriendlyByteBuf(Unpooled.buffer());
buf.writeByte(packetIds.getInt(message.getClass()));
message.toBytes(buf);
return buf;
}
public static ClientboundCustomPayloadPacket encodeClient(NetworkMessage<ClientNetworkContext> message) {
return new ClientboundCustomPayloadPacket(ID, encode(message));
}
public static ServerboundCustomPayloadPacket encodeServer(NetworkMessage<ServerNetworkContext> message) {
return new ServerboundCustomPayloadPacket(ID, encode(message));
}
@Nullable
private static <T> NetworkMessage<T> decode(Int2ObjectMap<Function<FriendlyByteBuf, ? extends NetworkMessage<T>>> packets, FriendlyByteBuf buffer) {
int type = buffer.readByte();
var reader = packets.get(type);
if (reader == null) {
LOGGER.debug("Unknown packet {}", type);
return null;
}
return reader.apply(buffer);
}
@Nullable
public static NetworkMessage<ServerNetworkContext> decodeServer(FriendlyByteBuf buffer) {
return decode(serverPackets, buffer);
}
@Nullable
public static NetworkMessage<ClientNetworkContext> decodeClient(FriendlyByteBuf buffer) {
return decode(clientPackets, buffer);
}
}

View File

@@ -17,6 +17,7 @@ import dan200.computercraft.api.peripheral.PeripheralLookup;
import dan200.computercraft.impl.Peripherals;
import dan200.computercraft.mixin.ArgumentTypeInfosAccessor;
import dan200.computercraft.shared.config.ConfigFile;
import dan200.computercraft.shared.network.MessageType;
import dan200.computercraft.shared.network.NetworkMessage;
import dan200.computercraft.shared.network.client.ClientNetworkContext;
import dan200.computercraft.shared.network.container.ContainerData;
@@ -27,6 +28,8 @@ import net.fabricmc.fabric.api.event.player.UseEntityCallback;
import net.fabricmc.fabric.api.itemgroup.v1.FabricItemGroup;
import net.fabricmc.fabric.api.lookup.v1.block.BlockApiCache;
import net.fabricmc.fabric.api.lookup.v1.block.BlockApiLookup;
import net.fabricmc.fabric.api.networking.v1.PacketByteBufs;
import net.fabricmc.fabric.api.networking.v1.ServerPlayNetworking;
import net.fabricmc.fabric.api.object.builder.v1.block.entity.FabricBlockEntityTypeBuilder;
import net.fabricmc.fabric.api.registry.FuelRegistry;
import net.fabricmc.fabric.api.resource.conditions.v1.DefaultResourceConditions;
@@ -44,6 +47,8 @@ import net.minecraft.core.Registry;
import net.minecraft.core.registries.BuiltInRegistries;
import net.minecraft.network.FriendlyByteBuf;
import net.minecraft.network.chat.Component;
import net.minecraft.network.protocol.Packet;
import net.minecraft.network.protocol.game.ClientGamePacketListener;
import net.minecraft.resources.ResourceKey;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.server.MinecraftServer;
@@ -170,31 +175,42 @@ public class PlatformHelperImpl implements PlatformHelper {
player.openMenu(new WrappedMenuProvider(owner, menu));
}
@Override
public <T extends NetworkMessage<?>> MessageType<T> createMessageType(int id, ResourceLocation channel, Class<T> klass, FriendlyByteBuf.Reader<T> reader) {
return new FabricMessageType<>(channel, reader);
}
private Packet<ClientGamePacketListener> encodeClientbound(NetworkMessage<ClientNetworkContext> message) {
var buf = PacketByteBufs.create();
message.write(buf);
return ServerPlayNetworking.createS2CPacket(FabricMessageType.toFabricType(message.type()).getId(), buf);
}
@Override
public void sendToPlayer(NetworkMessage<ClientNetworkContext> message, ServerPlayer player) {
player.connection.send(NetworkHandler.encodeClient(message));
player.connection.send(encodeClientbound(message));
}
@Override
public void sendToPlayers(NetworkMessage<ClientNetworkContext> message, Collection<ServerPlayer> players) {
if (players.isEmpty()) return;
var packet = NetworkHandler.encodeClient(message);
var packet = encodeClientbound(message);
for (var player : players) player.connection.send(packet);
}
@Override
public void sendToAllPlayers(NetworkMessage<ClientNetworkContext> message, MinecraftServer server) {
server.getPlayerList().broadcastAll(NetworkHandler.encodeClient(message));
server.getPlayerList().broadcastAll(encodeClientbound(message));
}
@Override
public void sendToAllAround(NetworkMessage<ClientNetworkContext> message, ServerLevel level, Vec3 pos, float distance) {
level.getServer().getPlayerList().broadcast(null, pos.x, pos.y, pos.z, distance, level.dimension(), NetworkHandler.encodeClient(message));
level.getServer().getPlayerList().broadcast(null, pos.x, pos.y, pos.z, distance, level.dimension(), encodeClientbound(message));
}
@Override
public void sendToAllTracking(NetworkMessage<ClientNetworkContext> message, LevelChunk chunk) {
var packet = NetworkHandler.encodeClient(message);
var packet = encodeClientbound(message);
for (var player : ((ServerChunkCache) chunk.getLevel().getChunkSource()).chunkMap.getPlayers(chunk.getPos(), false)) {
player.connection.send(packet);
}

View File

@@ -131,12 +131,13 @@ dependencies {
libs.bundles.externalMods.forge.runtime.get().map { runtimeOnly(fg.deobf(it)) }
// Depend on our other projects.
api(commonClasses(project(":forge-api")))
api(clientClasses(project(":forge-api")))
implementation(project(":core"))
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) {
jarJar.ranged(this, "[${libs.versions.cobalt.asProvider().get()},${libs.versions.cobalt.next.get()})")
val version = libs.versions.cobalt.get()
jarJar.ranged(this, "[$version,${getNextVersion(version)})")
}
minecraftEmbed(libs.jzlib) {
jarJar.ranged(this, "[${libs.versions.jzlib.get()},)")
@@ -254,7 +255,7 @@ publishing {
artifact(tasks.jarJar)
mavenDependencies {
exclude(dependencies.create("cc.tweaked:"))
cct.configureExcludes(this)
exclude(libs.jei.forge.get())
}
}

View File

@@ -1,7 +1,6 @@
{
"type": "computercraft:computer_upgrade",
"category": "redstone",
"family": "advanced",
"key": {"#": {"tag": "forge:ingots/gold"}, "C": {"item": "computercraft:computer_normal"}},
"pattern": ["###", "#C#", "# #"],
"result": {"item": "computercraft:computer_advanced"},

View File

@@ -1,7 +1,6 @@
{
"type": "computercraft:computer_upgrade",
"category": "redstone",
"family": "advanced",
"key": {"#": {"tag": "forge:ingots/gold"}, "C": {"item": "computercraft:pocket_computer_normal"}},
"pattern": ["###", "#C#", "# #"],
"result": {"item": "computercraft:pocket_computer_advanced"},

View File

@@ -1,7 +1,6 @@
{
"type": "computercraft:turtle",
"category": "redstone",
"family": "advanced",
"key": {
"#": {"tag": "forge:ingots/gold"},
"C": {"item": "computercraft:computer_advanced"},

View File

@@ -1,7 +1,6 @@
{
"type": "computercraft:computer_upgrade",
"category": "redstone",
"family": "advanced",
"key": {
"#": {"tag": "forge:ingots/gold"},
"B": {"tag": "forge:storage_blocks/gold"},

View File

@@ -1,7 +1,6 @@
{
"type": "computercraft:turtle",
"category": "redstone",
"family": "normal",
"key": {
"#": {"tag": "forge:ingots/iron"},
"C": {"item": "computercraft:computer_normal"},

View File

@@ -0,0 +1 @@
{"values": ["computercraft:computer_command"]}

View File

@@ -12,6 +12,7 @@ import dan200.computercraft.api.network.wired.WiredElement;
import dan200.computercraft.api.peripheral.IPeripheral;
import dan200.computercraft.api.pocket.PocketUpgradeSerialiser;
import dan200.computercraft.api.turtle.TurtleUpgradeSerialiser;
import dan200.computercraft.shared.CommonHooks;
import dan200.computercraft.shared.ModRegistry;
import dan200.computercraft.shared.config.ConfigSpec;
import dan200.computercraft.shared.details.FluidData;
@@ -23,6 +24,7 @@ import dan200.computercraft.shared.platform.ForgeConfigFile;
import dan200.computercraft.shared.platform.NetworkHandler;
import net.minecraftforge.common.capabilities.ForgeCapabilities;
import net.minecraftforge.common.capabilities.RegisterCapabilitiesEvent;
import net.minecraftforge.event.BuildCreativeModeTabContentsEvent;
import net.minecraftforge.eventbus.api.SubscribeEvent;
import net.minecraftforge.fml.ModList;
import net.minecraftforge.fml.ModLoadingContext;
@@ -100,4 +102,9 @@ public final class ComputerCraft {
ConfigSpec.syncClient(path);
}
}
@SubscribeEvent
public static void onCreativeTab(BuildCreativeModeTabContentsEvent event) {
CommonHooks.onBuildCreativeTab(event.getTabKey(), event.getParameters(), event);
}
}

View File

@@ -5,12 +5,11 @@
package dan200.computercraft.shared.platform;
import dan200.computercraft.api.ComputerCraftAPI;
import dan200.computercraft.shared.network.MessageType;
import dan200.computercraft.shared.network.NetworkMessage;
import dan200.computercraft.shared.network.NetworkMessages;
import dan200.computercraft.shared.network.client.ClientNetworkContext;
import dan200.computercraft.shared.network.server.ServerNetworkContext;
import it.unimi.dsi.fastutil.ints.IntOpenHashSet;
import it.unimi.dsi.fastutil.ints.IntSet;
import net.minecraft.network.FriendlyByteBuf;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.server.level.ServerPlayer;
@@ -47,20 +46,15 @@ public final class NetworkHandler {
}
public static void setup() {
IntSet usedIds = new IntOpenHashSet();
NetworkMessages.register(new NetworkMessages.PacketRegistry() {
@Override
public <T extends NetworkMessage<ClientNetworkContext>> void registerClientbound(int id, Class<T> type, Function<FriendlyByteBuf, T> decoder) {
if (!usedIds.add(id)) throw new IllegalArgumentException("Already have a packet with id " + id);
registerMainThread(id, NetworkDirection.PLAY_TO_CLIENT, type, decoder, x -> ClientNetworkContext.get());
}
for (var type : NetworkMessages.getServerbound()) {
var forgeType = (MessageTypeImpl<? extends NetworkMessage<ServerNetworkContext>>) type;
registerMainThread(forgeType, NetworkDirection.PLAY_TO_SERVER, c -> () -> assertNonNull(c.getSender()));
}
@Override
public <T extends NetworkMessage<ServerNetworkContext>> void registerServerbound(int id, Class<T> type, Function<FriendlyByteBuf, T> decoder) {
if (!usedIds.add(id)) throw new IllegalArgumentException("Already have a packet with id " + id);
registerMainThread(id, NetworkDirection.PLAY_TO_SERVER, type, decoder, c -> () -> assertNonNull(c.getSender()));
}
});
for (var type : NetworkMessages.getClientbound()) {
var forgeType = (MessageTypeImpl<? extends NetworkMessage<ClientNetworkContext>>) type;
registerMainThread(forgeType, NetworkDirection.PLAY_TO_CLIENT, x -> ClientNetworkContext.get());
}
}
static void sendToPlayer(NetworkMessage<ClientNetworkContext> packet, ServerPlayer player) {
@@ -96,19 +90,16 @@ public final class NetworkHandler {
*
* @param <T> The type of the packet to send.
* @param <H> The context this packet is evaluated under.
* @param type The class of the type of packet to send.
* @param id The identifier for this packet type.
* @param type The message type to register.
* @param direction A network direction which will be asserted before any processing of this message occurs
* @param decoder The factory for this type of packet.
* @param handler Gets or constructs the handler for this packet.
*/
static <H, T extends NetworkMessage<H>> void registerMainThread(
int id, NetworkDirection direction, Class<T> type, Function<FriendlyByteBuf, T> decoder,
Function<NetworkEvent.Context, H> handler
MessageTypeImpl<T> type, NetworkDirection direction, Function<NetworkEvent.Context, H> handler
) {
network.messageBuilder(type, id, direction)
.encoder(NetworkMessage::toBytes)
.decoder(decoder)
network.messageBuilder(type.klass(), type.id(), direction)
.encoder(NetworkMessage::write)
.decoder(type.reader())
.consumerMainThread((packet, contextSup) -> {
try {
packet.handle(handler.apply(contextSup.get()));
@@ -119,4 +110,9 @@ public final class NetworkHandler {
})
.add();
}
public record MessageTypeImpl<T extends NetworkMessage<?>>(
int id, Class<T> klass, Function<FriendlyByteBuf, T> reader
) implements MessageType<T> {
}
}

View File

@@ -15,6 +15,7 @@ import dan200.computercraft.api.peripheral.IPeripheral;
import dan200.computercraft.impl.Peripherals;
import dan200.computercraft.shared.Capabilities;
import dan200.computercraft.shared.config.ConfigFile;
import dan200.computercraft.shared.network.MessageType;
import dan200.computercraft.shared.network.NetworkMessage;
import dan200.computercraft.shared.network.client.ClientNetworkContext;
import dan200.computercraft.shared.network.container.ContainerData;
@@ -157,6 +158,11 @@ public class PlatformHelperImpl implements PlatformHelper {
NetworkHooks.openScreen((ServerPlayer) player, owner, menu::toBytes);
}
@Override
public <T extends NetworkMessage<?>> MessageType<T> createMessageType(int id, ResourceLocation channel, Class<T> klass, FriendlyByteBuf.Reader<T> reader) {
return new NetworkHandler.MessageTypeImpl<>(id, klass, reader);
}
@Override
public void sendToPlayer(NetworkMessage<ClientNetworkContext> message, ServerPlayer player) {
NetworkHandler.sendToPlayer(message, player);

View File

@@ -6,11 +6,15 @@ package cc.tweaked.standalone;
import dan200.computercraft.core.ComputerContext;
import dan200.computercraft.core.CoreConfig;
import dan200.computercraft.core.apis.http.options.Action;
import dan200.computercraft.core.apis.http.options.AddressRule;
import dan200.computercraft.core.computer.Computer;
import dan200.computercraft.core.terminal.Terminal;
import dan200.computercraft.core.terminal.TextBuffer;
import dan200.computercraft.core.util.Colour;
import org.apache.commons.cli.*;
import org.jetbrains.annotations.Contract;
import org.lwjgl.glfw.GLFW;
import org.lwjgl.glfw.GLFWErrorCallback;
import org.lwjgl.opengl.GL;
@@ -20,13 +24,16 @@ import org.lwjgl.system.MemoryStack;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.annotation.Nullable;
import java.io.IOException;
import java.io.PrintWriter;
import java.nio.ByteBuffer;
import java.nio.charset.StandardCharsets;
import java.nio.file.InvalidPathException;
import java.nio.file.Path;
import java.util.List;
import java.util.Objects;
import java.util.OptionalInt;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.regex.Pattern;
@@ -53,7 +60,7 @@ public class Main {
public static final Pattern PATTERN = Pattern.compile("^(\\d+)x(\\d+)$");
}
private static <T> T getParsedOptionValue(CommandLine cli, String opt, Class<T> klass) throws ParseException {
private static <T> T getParsedOptionValue(CommandLine cli, Option opt, Class<T> klass) throws ParseException {
var res = cli.getOptionValue(opt);
if (klass == Path.class) {
try {
@@ -71,30 +78,47 @@ public class Main {
}
}
@Contract("_, _, _, !null -> !null")
private static <T> @Nullable T getParsedOptionValue(CommandLine cli, Option opt, Class<T> klass, @Nullable T defaultValue) throws ParseException {
return cli.hasOption(opt) ? getParsedOptionValue(cli, opt, klass) : defaultValue;
}
public static void main(String[] args) throws InterruptedException {
var options = new Options();
options.addOption(Option.builder("r").argName("PATH").longOpt("resources").hasArg()
Option resourceOpt, computerOpt, termSizeOpt, allowLocalDomainsOpt, helpOpt;
options.addOption(resourceOpt = Option.builder("r").argName("PATH").longOpt("resources").hasArg()
.desc("The path to the resources directory")
.build());
options.addOption(Option.builder("c").argName("PATH").longOpt("computer").hasArg()
options.addOption(computerOpt = Option.builder("c").argName("PATH").longOpt("computer").hasArg()
.desc("The root directory of the computer. Defaults to a temporary directory.")
.build());
options.addOption(Option.builder("t").argName("WIDTHxHEIGHT").longOpt("term-size").hasArg()
.desc("The size of the terminal, defaults to 51x19")
options.addOption(termSizeOpt = Option.builder("t").argName("WIDTHxHEIGHT").longOpt("term-size").hasArg()
.desc("The size of the terminal, defaults to 51x19.")
.build());
options.addOption(allowLocalDomainsOpt = Option.builder("L").longOpt("allow-local-domains")
.desc("Allow accessing local domains with the HTTP API.")
.build());
options.addOption(new Option("h", "help", false, "Print help message"));
options.addOption(helpOpt = Option.builder("h").longOpt("help")
.desc("Print help message")
.build());
Path resourcesDirectory;
Path computerDirectory;
TermSize termSize;
boolean allowLocalDomains;
try {
var cli = new DefaultParser().parse(options, args);
if (!cli.hasOption("r")) throw new ParseException("--resources directory is required");
if (cli.hasOption(helpOpt)) {
new HelpFormatter().printHelp("standalone.jar", options, true);
return;
}
if (!cli.hasOption(resourceOpt)) throw new ParseException("--resources directory is required");
resourcesDirectory = getParsedOptionValue(cli, "r", Path.class);
computerDirectory = cli.hasOption("c") ? getParsedOptionValue(cli, "c", Path.class) : null;
termSize = cli.hasOption("t") ? getParsedOptionValue(cli, "t", TermSize.class) : TermSize.DEFAULT;
resourcesDirectory = getParsedOptionValue(cli, resourceOpt, Path.class);
computerDirectory = getParsedOptionValue(cli, computerOpt, Path.class, null);
termSize = getParsedOptionValue(cli, termSizeOpt, TermSize.class, TermSize.DEFAULT);
allowLocalDomains = cli.hasOption(allowLocalDomainsOpt);
} catch (ParseException e) {
System.err.println(e.getLocalizedMessage());
@@ -106,6 +130,10 @@ public class Main {
return;
}
if (allowLocalDomains) {
CoreConfig.httpRules = List.of(AddressRule.parse("*", OptionalInt.empty(), Action.ALLOW.toPartial()));
}
var context = ComputerContext.builder(new StandaloneGlobalEnvironment(resourcesDirectory)).build();
try (var gl = new GLObjects()) {
var isDirty = new AtomicBoolean(true);