1
0
mirror of https://github.com/SquidDev-CC/CC-Tweaked synced 2025-09-04 19:37:55 +00:00

Update to Minecraft 1.21.5

0/10, would not recommend. Though increasingly feeling that about
modding as a whole — really not feeling as emotionally rewarding as it
once did.

Server-side changes are well, not simple, but relatively straightforward:

 - Block removal code is now called before the BE is removed, not after.

   - Monitors now need to track if they've being removed or not again.

   - Turtle drop consuming code no longer tries to insert items into
     the turtle immediately, instead waiting 'til the action is
     complete. Otherwise if the turtle gets destroyed mid-action
     (e.g. the block explodes), then it tries to insert its drops into
     itself!

     We previously guarded against this by checking if the turtle BE had
     been removed, but obviously this no longer works, so just easier to
     shift the insertion.

  - The interface for reading/writing NBT has been overhauled. It has
    native "getOr" and codec support (nice!) but also has been changed
    again in the latest snapshot (less nice!).

 - The dye item component no longer has a "hide tooltip" flag. We now
   hide the tooltip with a default component instead.

 - Related to the above, we can now do all the tooltip-related things we
   needed to do with vanilla's TooltipProvider. This did require
   splitting NonNegativeId into subclasses for disk/computer, but
   otherwise is quite nice.

 - Some changes to model datagen. Annoying, but boring.

 - Game tests got a complete overhaul. I'm keeping the interface the
   same for now (@GameTest), because I'm blowed if I'm datagenning test
   instances :p. If it's any consolation, both NF and Fabric are doing
   this too.

Client changes are a bit more involved though:

 - VertexBuffer has been entirely removed. We now construct the
   GpuBuffer directly.

 - BakedModel is gone! Oh this caused so much suffering for turtle
   models. I ended up rewriting the whole system in processes (which
   then involved PRs to NF and Fabric). Rather than returning a
   TransformedModel, turtle models are now responsible for rendering the
   model.

   This may see another rewrite in the future. I'd like to switch to
   JSON-based turtle models (like item models), but that's blocked on
   some changes to NF right now.

   Sorry to all add-on devs, I know this is a big change.
This commit is contained in:
Jonathan Coates
2025-04-30 22:31:14 +01:00
parent a939ad8b97
commit a1df196673
161 changed files with 2276 additions and 1997 deletions

View File

@@ -9,7 +9,11 @@ import com.mojang.brigadier.builder.LiteralArgumentBuilder;
import com.mojang.brigadier.builder.RequiredArgumentBuilder;
import dan200.computercraft.api.ComputerCraftAPI;
import dan200.computercraft.api.client.FabricComputerCraftAPIClient;
import dan200.computercraft.api.client.StandaloneModel;
import dan200.computercraft.api.client.turtle.TurtleUpgradeModel;
import dan200.computercraft.api.turtle.ITurtleUpgrade;
import dan200.computercraft.client.model.ExtraModels;
import dan200.computercraft.client.platform.FabricModelKey;
import dan200.computercraft.core.util.Nullability;
import dan200.computercraft.impl.Services;
import dan200.computercraft.shared.CommonHooks;
@@ -25,6 +29,8 @@ import net.fabricmc.fabric.api.client.command.v2.FabricClientCommandSource;
import net.fabricmc.fabric.api.client.event.lifecycle.v1.ClientTickEvents;
import net.fabricmc.fabric.api.client.item.v1.ItemTooltipCallback;
import net.fabricmc.fabric.api.client.model.loading.v1.PreparableModelLoadingPlugin;
import net.fabricmc.fabric.api.client.model.loading.v1.SimpleUnbakedExtraModel;
import net.fabricmc.fabric.api.client.model.loading.v1.UnbakedExtraModel;
import net.fabricmc.fabric.api.client.networking.v1.ClientPlayNetworking;
import net.fabricmc.fabric.api.client.rendering.v1.WorldRenderEvents;
import net.fabricmc.loader.api.FabricLoader;
@@ -36,6 +42,7 @@ import net.minecraft.client.renderer.RenderType;
import net.minecraft.client.renderer.item.ItemModels;
import net.minecraft.client.renderer.item.properties.conditional.ConditionalItemModelProperties;
import net.minecraft.client.renderer.item.properties.select.SelectItemModelProperties;
import net.minecraft.client.resources.model.ModelBaker;
import net.minecraft.world.phys.BlockHitResult;
import java.nio.file.Files;
@@ -54,7 +61,7 @@ public class ComputerCraftClient {
}
ClientRegistry.register();
ClientRegistry.registerTurtleModellers(FabricComputerCraftAPIClient::registerTurtleUpgradeModeller);
ClientRegistry.registerTurtleModels(FabricComputerCraftAPIClient::registerTurtleUpgradeModeller);
ClientRegistry.registerMenuScreens(MenuScreens::register);
ClientRegistry.registerItemModels(ItemModels.ID_MAPPER::put);
ClientRegistry.registerItemColours(ItemTintSources.ID_MAPPER::put);
@@ -63,7 +70,11 @@ public class ComputerCraftClient {
PreparableModelLoadingPlugin.register(
(resources, executor) -> CompletableFuture.supplyAsync(() -> ExtraModels.loadAll(resources), executor),
(state, context) -> ClientRegistry.registerExtraModels(context::addModels, state)
(state, context) -> ClientRegistry.registerExtraModels(
(key, model) -> context.addModel(FabricModelKey.key(key), new SimpleUnbakedExtraModel<>(model, StandaloneModel::of)),
(key, model) -> context.addModel(FabricModelKey.erased(key), new TurtleModelWrapper<>(model)),
state
)
);
BlockRenderLayerMap.INSTANCE.putBlock(ModRegistry.Blocks.COMPUTER_NORMAL.get(), RenderType.cutout());
@@ -100,4 +111,18 @@ public class ComputerCraftClient {
((FabricConfigFile) ConfigSpec.clientSpec).load(FabricLoader.getInstance().getConfigDir().resolve(ComputerCraftAPI.MOD_ID + "-client.toml"));
}
private record TurtleModelWrapper<T extends ITurtleUpgrade>(
TurtleUpgradeModel.Unbaked<T> model
) implements UnbakedExtraModel<TurtleUpgradeModel<T>> {
@Override
public TurtleUpgradeModel<T> bake(ModelBaker baker) {
return model().bake(baker);
}
@Override
public void resolveDependencies(Resolver resolver) {
model().resolveDependencies(resolver);
}
}
}

View File

@@ -5,36 +5,14 @@
package dan200.computercraft.client.platform;
import com.google.auto.service.AutoService;
import com.mojang.blaze3d.vertex.PoseStack;
import dan200.computercraft.client.render.ModelRenderer;
import net.fabricmc.fabric.api.renderer.v1.model.ModelHelper;
import net.minecraft.client.renderer.MultiBufferSource;
import net.minecraft.client.renderer.Sheets;
import net.minecraft.client.resources.model.BakedModel;
import net.minecraft.client.resources.model.ModelManager;
import net.fabricmc.fabric.api.client.model.loading.v1.ExtraModelKey;
import net.minecraft.client.resources.model.ModelDebugName;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.util.RandomSource;
import org.jspecify.annotations.Nullable;
@AutoService(dan200.computercraft.impl.client.ClientPlatformHelper.class)
@AutoService(ClientPlatformHelper.class)
public class ClientPlatformHelperImpl implements ClientPlatformHelper {
private static final RandomSource random = RandomSource.create(0);
@Override
public BakedModel getModel(ModelManager manager, ResourceLocation resourceLocation) {
var model = manager.getModel(resourceLocation);
return model == null ? manager.getMissingModel() : model;
}
@Override
public void renderBakedModel(PoseStack transform, MultiBufferSource buffers, BakedModel model, int lightmapCoord, int overlayLight, int @Nullable [] tints) {
// Unfortunately we can't call Fabric's emitItemQuads here, as there's no way to obtain a RenderContext via the
// API. Instead, we special case our FoiledModel, and just render everything else normally.
var buffer = buffers.getBuffer(Sheets.translucentItemSheet());
for (var faceIdx = 0; faceIdx <= ModelHelper.NULL_FACE_ID; faceIdx++) {
var face = ModelHelper.faceFromIndex(faceIdx);
random.setSeed(42);
ModelRenderer.renderQuads(transform, buffer, model.getQuads(null, face, random), lightmapCoord, overlayLight, tints);
}
public <T> ModelKey<T> createModelKey(ResourceLocation id, ModelDebugName name) {
return new FabricModelKey<>(ExtraModelKey.create(name::debugName));
}
}

View File

@@ -0,0 +1,36 @@
// SPDX-FileCopyrightText: 2025 The CC: Tweaked Developers
//
// SPDX-License-Identifier: MPL-2.0
package dan200.computercraft.client.platform;
import net.fabricmc.fabric.api.client.model.loading.v1.ExtraModelKey;
import net.minecraft.client.resources.model.ModelManager;
import org.jspecify.annotations.Nullable;
/**
* Wraps a Fabric {@link ExtraModelKey} into our multi-loader {@link ModelKey}.
*
* @param <T> The type of the baked model.
*/
public final class FabricModelKey<T> implements ModelKey<T> {
private final ExtraModelKey<T> key;
FabricModelKey(ExtraModelKey<T> key) {
this.key = key;
}
public static <T> ExtraModelKey<T> key(ModelKey<T> key) {
return ((FabricModelKey<T>) key).key;
}
@SuppressWarnings("unchecked")
public static <T> ExtraModelKey<T> erased(ModelKey<?> key) {
return ((FabricModelKey<T>) key).key;
}
@Override
public @Nullable T get(ModelManager manager) {
return manager.getModel(key);
}
}

View File

@@ -5,15 +5,15 @@
package dan200.computercraft.impl.client;
import com.google.auto.service.AutoService;
import dan200.computercraft.api.client.turtle.TurtleUpgradeModeller;
import dan200.computercraft.api.client.turtle.TurtleUpgradeModel;
import dan200.computercraft.api.turtle.ITurtleUpgrade;
import dan200.computercraft.api.upgrades.UpgradeType;
import dan200.computercraft.client.turtle.TurtleUpgradeModellers;
import dan200.computercraft.client.turtle.TurtleUpgradeModels;
@AutoService(FabricComputerCraftAPIClientService.class)
public final class FabricComputerCraftAPIClientImpl implements FabricComputerCraftAPIClientService {
@Override
public <T extends ITurtleUpgrade> void registerTurtleUpgradeModeller(UpgradeType<T> type, TurtleUpgradeModeller<T> modeller) {
TurtleUpgradeModellers.register(type, modeller);
public <T extends ITurtleUpgrade> void registerTurtleUpgradeModeller(UpgradeType<T> type, TurtleUpgradeModel.Unbaked<? super T> modeller) {
TurtleUpgradeModels.register(type, modeller);
}
}

View File

@@ -1,66 +0,0 @@
// SPDX-FileCopyrightText: 2022 The CC: Tweaked Developers
//
// SPDX-License-Identifier: MPL-2.0
package dan200.computercraft.mixin.client;
import com.mojang.blaze3d.vertex.PoseStack;
import com.mojang.blaze3d.vertex.VertexConsumer;
import dan200.computercraft.client.ClientHooks;
import net.minecraft.client.renderer.block.BlockModelShaper;
import net.minecraft.client.renderer.block.BlockRenderDispatcher;
import net.minecraft.client.renderer.block.ModelBlockRenderer;
import net.minecraft.client.renderer.texture.OverlayTexture;
import net.minecraft.core.BlockPos;
import net.minecraft.util.RandomSource;
import net.minecraft.world.level.BlockAndTintGetter;
import net.minecraft.world.level.block.state.BlockState;
import org.spongepowered.asm.mixin.Final;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Shadow;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
/**
* Provides custom block breaking progress for modems, so it only applies to the current part.
*
* @see BlockRenderDispatcher#renderBreakingTexture(BlockState, BlockPos, BlockAndTintGetter, PoseStack, VertexConsumer)
*/
@Mixin(BlockRenderDispatcher.class)
class BlockRenderDispatcherMixin {
@Shadow
@Final
private RandomSource random;
@Shadow
@Final
private BlockModelShaper blockModelShaper;
@Shadow
@Final
private ModelBlockRenderer modelRenderer;
@Inject(
method = "renderBreakingTexture",
at = @At("HEAD"),
cancellable = true,
require = 0 // This isn't critical functionality, so don't worry if we can't apply it.
)
@SuppressWarnings("UnusedMethod")
private void renderBlockDamage(
BlockState state, BlockPos pos, BlockAndTintGetter world, PoseStack pose, VertexConsumer buffers,
CallbackInfo info
) {
var newState = ClientHooks.getBlockBreakingState(state, pos);
if (newState != null) {
info.cancel();
var model = blockModelShaper.getBlockModel(newState);
modelRenderer.tesselateBlock(
world, model, newState, pos, pose, buffers, true, random, newState.getSeed(pos),
OverlayTexture.NO_OVERLAY
);
}
}
}

View File

@@ -7,7 +7,6 @@
"defaultRequire": 1
},
"client": [
"BlockRenderDispatcherMixin",
"DebugScreenOverlayMixin",
"ItemFrameRendererMixin",
"ItemFrameRenderStateMixin",

View File

@@ -1,14 +1,14 @@
package com.example.examplemod;
import dan200.computercraft.api.client.FabricComputerCraftAPIClient;
import dan200.computercraft.api.client.turtle.TurtleUpgradeModeller;
import dan200.computercraft.api.client.turtle.TurtleUpgradeModel;
import net.fabricmc.api.ClientModInitializer;
public class FabricExampleModClient implements ClientModInitializer {
@Override
public void onInitializeClient() {
// @start region=turtle_modellers
FabricComputerCraftAPIClient.registerTurtleUpgradeModeller(ExampleMod.EXAMPLE_TURTLE_UPGRADE, TurtleUpgradeModeller.flatItem());
// @end region=turtle_modellers
// @start region=turtle_model
FabricComputerCraftAPIClient.registerTurtleUpgradeModeller(ExampleMod.EXAMPLE_TURTLE_UPGRADE, TurtleUpgradeModel.flatItem());
// @end region=turtle_model
}
}

View File

@@ -241,8 +241,7 @@ public class PlatformHelperImpl implements PlatformHelper {
@Override
public ClickEvent createOpenFolderAction(Path path) {
return new ClickEvent(
ClickEvent.Action.RUN_COMMAND,
return new ClickEvent.RunCommand(
"/" + ComputerCraft.CLIENT_OPEN_FOLDER + " " + StringArgumentType.escapeIfRequired(path.toAbsolutePath().toString())
);
}

View File

@@ -39,6 +39,10 @@
"mixins": [
"computercraft.mixins.json",
"computercraft.fabric.mixins.json",
{
"config": "computercraft-client.mixins.json",
"environment": "client"
},
{
"config": "computercraft-client.fabric.mixins.json",
"environment": "client"
@@ -46,8 +50,8 @@
],
"depends": {
"fabricloader": ">=0.16.10",
"fabric-api": ">=0.118.0",
"minecraft": "=1.21.4"
"fabric-api": ">=0.119.5",
"minecraft": "=1.21.5"
},
"accessWidener": "computercraft.accesswidener"
}

View File

@@ -6,6 +6,8 @@ package dan200.computercraft.gametest.core;
import dan200.computercraft.api.ComputerCraftAPI;
import dan200.computercraft.export.Exporter;
import dan200.computercraft.gametest.api.ClientTestEnvironment;
import dan200.computercraft.mixin.gametest.RegistryDataLoaderLoaderAccessor;
import net.fabricmc.api.ClientModInitializer;
import net.fabricmc.api.ModInitializer;
import net.fabricmc.fabric.api.client.command.v2.ClientCommandRegistrationCallback;
@@ -15,10 +17,21 @@ import net.fabricmc.fabric.api.event.Event;
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.minecraft.gametest.framework.GameTestRegistry;
import net.minecraft.core.Registry;
import net.minecraft.core.registries.BuiltInRegistries;
import net.minecraft.core.registries.Registries;
import net.minecraft.gametest.framework.GameTestInstance;
import net.minecraft.resources.ResourceKey;
import net.minecraft.resources.ResourceLocation;
import org.jspecify.annotations.Nullable;
import java.util.IdentityHashMap;
import java.util.List;
import java.util.Objects;
public class TestMod implements ModInitializer, ClientModInitializer {
private static @Nullable List<TestInstance> tests = null;
@Override
public void onInitialize() {
TestHooks.init();
@@ -29,7 +42,10 @@ public class TestMod implements ModInitializer, ClientModInitializer {
CommandRegistrationCallback.EVENT.register((dispatcher, buildContext, environment) -> CCTestCommand.register(dispatcher, buildContext));
PlayerBlockBreakEvents.BEFORE.register((level, player, pos, state, blockEntity) -> !TestHooks.onBeforeDestroyBlock(level, pos, state));
TestHooks.loadTests(GameTestRegistry::register);
Registry.register(BuiltInRegistries.TEST_ENVIRONMENT_DEFINITION_TYPE, ResourceLocation.fromNamespaceAndPath(TestHooks.MOD_ID, "client"), ClientTestEnvironment.CODEC);
var tests = TestMod.tests = TestHooks.loadTests();
for (var test : tests) Registry.register(BuiltInRegistries.TEST_FUNCTION, test.getId(), test.getFunction());
}
@Override
@@ -38,4 +54,17 @@ public class TestMod implements ModInitializer, ClientModInitializer {
ScreenEvents.AFTER_INIT.register((client, screen, scaledWidth, scaledHeight) -> ClientTestHooks.onOpenScreen(screen));
ClientCommandRegistrationCallback.EVENT.register((dispatcher, registryAccess) -> Exporter.register(dispatcher));
}
public static void registerDynamicEntries(List<RegistryDataLoaderLoaderAccessor<?>> registriesList) {
var registries = new IdentityHashMap<ResourceKey<? extends Registry<?>>, Registry<?>>(registriesList.size());
for (var entry : registriesList) registries.put(entry.getRegistry().key(), entry.getRegistry());
@SuppressWarnings("unchecked") var testInstances = (Registry<GameTestInstance>) registries.get(Registries.TEST_INSTANCE);
if (testInstances == null) return;
for (var test : Objects.requireNonNull(tests)) {
if (!testInstances.containsKey(test.getId())) {
Registry.register(testInstances, test.getId(), test.getInstance());
}
}
}
}

View File

@@ -0,0 +1,19 @@
// SPDX-FileCopyrightText: 2025 The CC: Tweaked Developers
//
// SPDX-License-Identifier: MPL-2.0
package dan200.computercraft.mixin.gametest;
import net.minecraft.core.WritableRegistry;
import net.minecraft.resources.RegistryDataLoader;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.gen.Accessor;
@Mixin(targets = "net/minecraft/resources/RegistryDataLoader$Loader")
public interface RegistryDataLoaderLoaderAccessor<T> {
@Accessor("data")
RegistryDataLoader.RegistryData<T> getData();
@Accessor("registry")
WritableRegistry<T> getRegistry();
}

View File

@@ -0,0 +1,40 @@
// SPDX-FileCopyrightText: 2025 The CC: Tweaked Developers
//
// SPDX-License-Identifier: MPL-2.0
package dan200.computercraft.mixin.gametest;
import com.llamalad7.mixinextras.sugar.Local;
import dan200.computercraft.gametest.core.TestMod;
import net.minecraft.core.HolderLookup;
import net.minecraft.core.RegistryAccess;
import net.minecraft.resources.RegistryDataLoader;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Coerce;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
import java.util.List;
/**
* Register game tests into the dynamic registry. This just mirrors Fabric's own hook, but loading our annotations
* instead.
*/
@Mixin(RegistryDataLoader.class)
class RegistryDataLoaderMixin {
@Inject(
method = "load(Lnet/minecraft/resources/RegistryDataLoader$LoadingFunction;Ljava/util/List;Ljava/util/List;)Lnet/minecraft/core/RegistryAccess$Frozen;",
at = @At(value = "INVOKE", target = "Ljava/util/List;forEach(Ljava/util/function/Consumer;)V", ordinal = 1)
)
@SuppressWarnings("unused")
private static void beforeFreeze(
@Coerce Object loadable,
List<HolderLookup.RegistryLookup<?>> wrappers,
List<RegistryDataLoader.RegistryData<?>> entries,
CallbackInfoReturnable<RegistryAccess.Frozen> cir,
@Local(ordinal = 2) List<RegistryDataLoaderLoaderAccessor<?>> registriesList
) {
TestMod.registerDynamicEntries(registriesList);
}
}

View File

@@ -0,0 +1,13 @@
{
"required": true,
"package": "dan200.computercraft.mixin.gametest",
"minVersion": "0.8",
"compatibilityLevel": "JAVA_21",
"injectors": {
"defaultRequire": 1
},
"mixins": [
"RegistryDataLoaderMixin",
"RegistryDataLoaderLoaderAccessor"
]
}

View File

@@ -1,6 +1,6 @@
{
"schemaVersion": 1,
"id": "cc-datagen",
"id": "cctest",
"version": "1.0.0",
"entrypoints": {
"main": [
@@ -11,7 +11,8 @@
]
},
"mixins": [
"computercraft-gametest.mixins.json"
"computercraft-gametest.mixins.json",
"computercraft-gametest.fabric.mixins.json"
],
"depends": {
"computercraft": "*"