mirror of
https://github.com/SquidDev-CC/CC-Tweaked
synced 2025-11-01 06:03:00 +00:00
Merge branch 'mc-1.19.x' into mc-1.20.x
This commit is contained in:
@@ -122,7 +122,7 @@ loom {
|
||||
|
||||
register("data") {
|
||||
configName = "Datagen"
|
||||
server()
|
||||
client()
|
||||
|
||||
runDir("run/dataGen")
|
||||
property("cct.pretty-json")
|
||||
@@ -168,7 +168,6 @@ tasks.processResources {
|
||||
filesMatching("fabric.mod.json") {
|
||||
expand(mapOf("version" to modVersion))
|
||||
}
|
||||
exclude(".cache")
|
||||
}
|
||||
|
||||
tasks.jar {
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
|
||||
package dan200.computercraft.client;
|
||||
|
||||
import dan200.computercraft.client.model.EmissiveComputerModel;
|
||||
import dan200.computercraft.client.model.turtle.TurtleModelLoader;
|
||||
import dan200.computercraft.shared.ModRegistry;
|
||||
import dan200.computercraft.shared.network.client.ClientNetworkContext;
|
||||
@@ -35,9 +36,12 @@ public class ComputerCraftClient {
|
||||
ClientRegistry.registerItemColours(ColorProviderRegistry.ITEM::register);
|
||||
ClientRegistry.registerMainThread();
|
||||
|
||||
|
||||
ModelLoadingRegistry.INSTANCE.registerModelProvider((manager, out) -> ClientRegistry.registerExtraModels(out));
|
||||
ModelLoadingRegistry.INSTANCE.registerResourceProvider(loader -> (path, ctx) -> TurtleModelLoader.load(loader, path));
|
||||
ModelLoadingRegistry.INSTANCE.registerResourceProvider(loader -> (path, ctx) -> EmissiveComputerModel.load(loader, path));
|
||||
BlockRenderLayerMap.INSTANCE.putBlock(ModRegistry.Blocks.COMPUTER_NORMAL.get(), RenderType.cutout());
|
||||
BlockRenderLayerMap.INSTANCE.putBlock(ModRegistry.Blocks.COMPUTER_COMMAND.get(), RenderType.cutout());
|
||||
BlockRenderLayerMap.INSTANCE.putBlock(ModRegistry.Blocks.COMPUTER_ADVANCED.get(), RenderType.cutout());
|
||||
BlockRenderLayerMap.INSTANCE.putBlock(ModRegistry.Blocks.MONITOR_NORMAL.get(), RenderType.cutout());
|
||||
BlockRenderLayerMap.INSTANCE.putBlock(ModRegistry.Blocks.MONITOR_ADVANCED.get(), RenderType.cutout());
|
||||
|
||||
|
||||
@@ -0,0 +1,172 @@
|
||||
// SPDX-FileCopyrightText: 2023 The CC: Tweaked Developers
|
||||
//
|
||||
// SPDX-License-Identifier: MPL-2.0
|
||||
|
||||
package dan200.computercraft.client.model;
|
||||
|
||||
import com.google.gson.JsonObject;
|
||||
import com.mojang.datafixers.util.Either;
|
||||
import dan200.computercraft.api.ComputerCraftAPI;
|
||||
import net.fabricmc.fabric.api.client.model.ModelProviderException;
|
||||
import net.fabricmc.fabric.api.client.model.ModelResourceProvider;
|
||||
import net.fabricmc.fabric.api.renderer.v1.RendererAccess;
|
||||
import net.fabricmc.fabric.api.renderer.v1.material.RenderMaterial;
|
||||
import net.fabricmc.fabric.api.renderer.v1.model.FabricBakedModel;
|
||||
import net.fabricmc.fabric.api.renderer.v1.model.ForwardingBakedModel;
|
||||
import net.fabricmc.fabric.api.renderer.v1.model.ModelHelper;
|
||||
import net.fabricmc.fabric.api.renderer.v1.render.RenderContext;
|
||||
import net.minecraft.client.renderer.block.model.BlockModel;
|
||||
import net.minecraft.client.renderer.block.model.ItemTransforms;
|
||||
import net.minecraft.client.renderer.texture.TextureAtlasSprite;
|
||||
import net.minecraft.client.resources.model.*;
|
||||
import net.minecraft.core.BlockPos;
|
||||
import net.minecraft.resources.ResourceLocation;
|
||||
import net.minecraft.server.packs.resources.ResourceManager;
|
||||
import net.minecraft.util.GsonHelper;
|
||||
import net.minecraft.util.RandomSource;
|
||||
import net.minecraft.world.inventory.InventoryMenu;
|
||||
import net.minecraft.world.item.ItemStack;
|
||||
import net.minecraft.world.level.BlockAndTintGetter;
|
||||
import net.minecraft.world.level.block.state.BlockState;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
import java.io.IOException;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.function.Function;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
/**
|
||||
* Wraps a computer's {@link BlockModel}/{@link BakedModel} to render the computer's cursor as an emissive quad.
|
||||
* <p>
|
||||
* While Fabric has a quite advanced rendering extension API (including support for custom materials), but unlike Forge
|
||||
* it doesn't expose this in the model JSON (though externals mods like <a href="https://github.com/vram-guild/json-model-extensions/">JMX</a>
|
||||
* do handle this).
|
||||
* <p>
|
||||
* Instead, we support emissive quads by injecting a custom {@linkplain ModelResourceProvider model loader/provider}
|
||||
* which targets a hard-coded list of computer models, and wraps the returned model in a custom
|
||||
* {@linkplain FabricBakedModel} implementation which renders specific quads as emissive.
|
||||
* <p>
|
||||
* See also the <code>assets/computercraft/models/block/computer_on.json</code> model, which is the base for all
|
||||
* emissive computer models.
|
||||
*/
|
||||
public final class EmissiveComputerModel {
|
||||
private static final Set<String> MODELS = Set.of(
|
||||
"item/computer_advanced",
|
||||
"block/computer_advanced_on",
|
||||
"block/computer_advanced_blinking",
|
||||
"item/computer_command",
|
||||
"block/computer_command_on",
|
||||
"block/computer_command_blinking",
|
||||
"item/computer_normal",
|
||||
"block/computer_normal_on",
|
||||
"block/computer_normal_blinking"
|
||||
);
|
||||
|
||||
private EmissiveComputerModel() {
|
||||
}
|
||||
|
||||
public static @Nullable UnbakedModel load(ResourceManager resources, ResourceLocation path) throws ModelProviderException {
|
||||
if (!path.getNamespace().equals(ComputerCraftAPI.MOD_ID) || !MODELS.contains(path.getPath())) return null;
|
||||
|
||||
JsonObject json;
|
||||
try (var reader = resources.openAsReader(new ResourceLocation(path.getNamespace(), "models/" + path.getPath() + ".json"))) {
|
||||
json = GsonHelper.parse(reader).getAsJsonObject();
|
||||
} catch (IOException e) {
|
||||
throw new ModelProviderException("Failed loading model " + path, e);
|
||||
}
|
||||
|
||||
// Parse a subset of the model JSON
|
||||
var parent = new ResourceLocation(GsonHelper.getAsString(json, "parent"));
|
||||
|
||||
Map<String, Either<Material, String>> textures = new HashMap<>();
|
||||
if (json.has("textures")) {
|
||||
var jsonObject = GsonHelper.getAsJsonObject(json, "textures");
|
||||
|
||||
for (var entry : jsonObject.entrySet()) {
|
||||
var texture = entry.getValue().getAsString();
|
||||
textures.put(entry.getKey(), texture.startsWith("#")
|
||||
? Either.right(texture.substring(1))
|
||||
: Either.left(new Material(InventoryMenu.BLOCK_ATLAS, new ResourceLocation(texture)))
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return new Unbaked(parent, textures);
|
||||
}
|
||||
|
||||
/**
|
||||
* An {@link UnbakedModel} which wraps the returned model using {@link Baked}.
|
||||
* <p>
|
||||
* This subclasses {@link BlockModel} to allow using these models as a parent of other models.
|
||||
*/
|
||||
private static final class Unbaked extends BlockModel {
|
||||
Unbaked(ResourceLocation parent, Map<String, Either<Material, String>> materials) {
|
||||
super(parent, List.of(), materials, null, null, ItemTransforms.NO_TRANSFORMS, List.of());
|
||||
}
|
||||
|
||||
@Override
|
||||
public BakedModel bake(ModelBaker baker, Function<Material, TextureAtlasSprite> spriteGetter, ModelState state, ResourceLocation location) {
|
||||
var baked = super.bake(baker, spriteGetter, state, location);
|
||||
if (!hasTexture("cursor")) return baked;
|
||||
|
||||
var render = RendererAccess.INSTANCE.getRenderer();
|
||||
if (render == null) return baked;
|
||||
|
||||
return new Baked(
|
||||
baked,
|
||||
spriteGetter.apply(getMaterial("cursor")),
|
||||
render.materialFinder().find(),
|
||||
render.materialFinder().emissive(0, true).find()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A {@link FabricBakedModel} which renders quads using the {@code "cursor"} texture as emissive.
|
||||
*/
|
||||
private static final class Baked extends ForwardingBakedModel {
|
||||
private final TextureAtlasSprite cursor;
|
||||
private final RenderMaterial defaultMaterial;
|
||||
private final RenderMaterial emissiveMaterial;
|
||||
|
||||
Baked(BakedModel wrapped, TextureAtlasSprite cursor, RenderMaterial defaultMaterial, RenderMaterial emissiveMaterial) {
|
||||
this.wrapped = wrapped;
|
||||
this.cursor = cursor;
|
||||
this.defaultMaterial = defaultMaterial;
|
||||
this.emissiveMaterial = emissiveMaterial;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isVanillaAdapter() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void emitBlockQuads(BlockAndTintGetter blockView, BlockState state, BlockPos pos, Supplier<RandomSource> randomSupplier, RenderContext context) {
|
||||
emitQuads(context, state, randomSupplier.get());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void emitItemQuads(ItemStack stack, Supplier<RandomSource> randomSupplier, RenderContext context) {
|
||||
emitQuads(context, null, randomSupplier.get());
|
||||
}
|
||||
|
||||
private void emitQuads(RenderContext context, @Nullable BlockState state, RandomSource random) {
|
||||
var emitter = context.getEmitter();
|
||||
for (var faceIdx = 0; faceIdx <= ModelHelper.NULL_FACE_ID; faceIdx++) {
|
||||
var cullFace = ModelHelper.faceFromIndex(faceIdx);
|
||||
var quads = wrapped.getQuads(state, cullFace, random);
|
||||
|
||||
var count = quads.size();
|
||||
for (var i = 0; i < count; i++) {
|
||||
final var q = quads.get(i);
|
||||
emitter.fromVanilla(q, q.getSprite() == cursor ? emissiveMaterial : defaultMaterial, cullFace);
|
||||
emitter.emit();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -4,84 +4,32 @@
|
||||
|
||||
package dan200.computercraft.client.model;
|
||||
|
||||
import com.mojang.blaze3d.vertex.DefaultVertexFormat;
|
||||
import com.mojang.blaze3d.vertex.VertexFormat;
|
||||
import com.mojang.blaze3d.vertex.VertexFormatElement;
|
||||
import com.mojang.math.Transformation;
|
||||
import dan200.computercraft.client.model.turtle.ModelTransformer;
|
||||
import net.minecraft.client.renderer.block.model.BakedQuad;
|
||||
import net.minecraft.client.resources.model.BakedModel;
|
||||
import net.minecraft.core.Direction;
|
||||
import net.minecraft.util.RandomSource;
|
||||
import net.minecraft.world.level.block.state.BlockState;
|
||||
import org.joml.Matrix4f;
|
||||
import org.joml.Vector4f;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* A {@link BakedModel} which applies a transformation matrix to its underlying quads.
|
||||
*
|
||||
* @see ModelTransformer
|
||||
*/
|
||||
public class TransformedBakedModel extends CustomBakedModel {
|
||||
private static final int STRIDE = DefaultVertexFormat.BLOCK.getIntegerSize();
|
||||
private static final int POS_OFFSET = findOffset(DefaultVertexFormat.BLOCK, DefaultVertexFormat.ELEMENT_POSITION);
|
||||
|
||||
private final Matrix4f transformation;
|
||||
private @Nullable TransformedQuads cache;
|
||||
private final ModelTransformer transformation;
|
||||
|
||||
public TransformedBakedModel(BakedModel model, Transformation transformation) {
|
||||
super(model);
|
||||
this.transformation = transformation.getMatrix();
|
||||
this.transformation = new ModelTransformer(transformation);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<BakedQuad> getQuads(@Nullable BlockState blockState, @Nullable Direction face, RandomSource rand) {
|
||||
var cache = this.cache;
|
||||
var quads = wrapped.getQuads(blockState, face, rand);
|
||||
if (quads.isEmpty()) return List.of();
|
||||
|
||||
// We do some basic caching here to avoid recomputing every frame. Most turtle models don't have culled faces,
|
||||
// so it's not worth being smarter here.
|
||||
if (cache != null && quads.equals(cache.original())) return cache.transformed();
|
||||
|
||||
List<BakedQuad> transformed = new ArrayList<>(quads.size());
|
||||
for (var quad : quads) transformed.add(transformQuad(quad));
|
||||
this.cache = new TransformedQuads(quads, transformed);
|
||||
return transformed;
|
||||
}
|
||||
|
||||
private BakedQuad transformQuad(BakedQuad quad) {
|
||||
var vertexData = quad.getVertices().clone();
|
||||
for (var i = 0; i < 4; i++) {
|
||||
// Apply the matrix to our position
|
||||
var start = STRIDE * i + POS_OFFSET;
|
||||
|
||||
var x = Float.intBitsToFloat(vertexData[start]);
|
||||
var y = Float.intBitsToFloat(vertexData[start + 1]);
|
||||
var z = Float.intBitsToFloat(vertexData[start + 2]);
|
||||
|
||||
// Transform the position
|
||||
var pos = new Vector4f(x, y, z, 1);
|
||||
transformation.transformProject(pos);
|
||||
|
||||
vertexData[start] = Float.floatToRawIntBits(pos.x());
|
||||
vertexData[start + 1] = Float.floatToRawIntBits(pos.y());
|
||||
vertexData[start + 2] = Float.floatToRawIntBits(pos.z());
|
||||
}
|
||||
|
||||
return new BakedQuad(vertexData, quad.getTintIndex(), quad.getDirection(), quad.getSprite(), quad.isShade());
|
||||
}
|
||||
|
||||
private record TransformedQuads(List<BakedQuad> original, List<BakedQuad> transformed) {
|
||||
}
|
||||
|
||||
private static int findOffset(VertexFormat format, VertexFormatElement element) {
|
||||
var offset = 0;
|
||||
for (var other : format.getElements()) {
|
||||
if (other == element) return offset / Integer.BYTES;
|
||||
offset += element.getByteSize();
|
||||
}
|
||||
throw new IllegalArgumentException("Cannot find " + element + " in " + format);
|
||||
return transformation.transform(wrapped.getQuads(blockState, face, rand));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
{
|
||||
"parent": "minecraft:block/orientable",
|
||||
"parent": "computercraft:block/computer_on",
|
||||
"textures": {
|
||||
"front": "computercraft:block/computer_advanced_front_blink",
|
||||
"cursor": "computercraft:block/computer_blink",
|
||||
"front": "computercraft:block/computer_advanced_front",
|
||||
"side": "computercraft:block/computer_advanced_side",
|
||||
"top": "computercraft:block/computer_advanced_top"
|
||||
}
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
{
|
||||
"parent": "minecraft:block/orientable",
|
||||
"parent": "computercraft:block/computer_on",
|
||||
"textures": {
|
||||
"front": "computercraft:block/computer_advanced_front_on",
|
||||
"cursor": "computercraft:block/computer_on",
|
||||
"front": "computercraft:block/computer_advanced_front",
|
||||
"side": "computercraft:block/computer_advanced_side",
|
||||
"top": "computercraft:block/computer_advanced_top"
|
||||
}
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
{
|
||||
"parent": "minecraft:block/orientable",
|
||||
"parent": "computercraft:block/computer_on",
|
||||
"textures": {
|
||||
"front": "computercraft:block/computer_command_front_blink",
|
||||
"cursor": "computercraft:block/computer_blink",
|
||||
"front": "computercraft:block/computer_command_front",
|
||||
"side": "computercraft:block/computer_command_side",
|
||||
"top": "computercraft:block/computer_command_top"
|
||||
}
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
{
|
||||
"parent": "minecraft:block/orientable",
|
||||
"parent": "computercraft:block/computer_on",
|
||||
"textures": {
|
||||
"front": "computercraft:block/computer_command_front_on",
|
||||
"cursor": "computercraft:block/computer_on",
|
||||
"front": "computercraft:block/computer_command_front",
|
||||
"side": "computercraft:block/computer_command_side",
|
||||
"top": "computercraft:block/computer_command_top"
|
||||
}
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
{
|
||||
"parent": "minecraft:block/orientable",
|
||||
"parent": "computercraft:block/computer_on",
|
||||
"textures": {
|
||||
"front": "computercraft:block/computer_normal_front_blink",
|
||||
"cursor": "computercraft:block/computer_blink",
|
||||
"front": "computercraft:block/computer_normal_front",
|
||||
"side": "computercraft:block/computer_normal_side",
|
||||
"top": "computercraft:block/computer_normal_top"
|
||||
}
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
{
|
||||
"parent": "minecraft:block/orientable",
|
||||
"parent": "computercraft:block/computer_on",
|
||||
"textures": {
|
||||
"front": "computercraft:block/computer_normal_front_on",
|
||||
"cursor": "computercraft:block/computer_on",
|
||||
"front": "computercraft:block/computer_normal_front",
|
||||
"side": "computercraft:block/computer_normal_side",
|
||||
"top": "computercraft:block/computer_normal_top"
|
||||
}
|
||||
|
||||
6
projects/fabric/src/generated/resources/assets/minecraft/atlases/blocks.json
generated
Normal file
6
projects/fabric/src/generated/resources/assets/minecraft/atlases/blocks.json
generated
Normal file
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"sources": [
|
||||
{"type": "minecraft:single", "resource": "computercraft:gui/turtle_upgrade_left"},
|
||||
{"type": "minecraft:single", "resource": "computercraft:gui/turtle_upgrade_right"}
|
||||
]
|
||||
}
|
||||
@@ -4,21 +4,25 @@
|
||||
|
||||
package dan200.computercraft.data;
|
||||
|
||||
import com.mojang.serialization.Codec;
|
||||
import dan200.computercraft.shared.platform.RegistryWrappers;
|
||||
import net.fabricmc.fabric.api.datagen.v1.DataGeneratorEntrypoint;
|
||||
import net.fabricmc.fabric.api.datagen.v1.FabricDataGenerator;
|
||||
import net.fabricmc.fabric.api.datagen.v1.FabricDataOutput;
|
||||
import net.fabricmc.fabric.api.datagen.v1.provider.FabricCodecDataProvider;
|
||||
import net.fabricmc.fabric.api.datagen.v1.provider.FabricModelProvider;
|
||||
import net.fabricmc.fabric.api.datagen.v1.provider.FabricTagProvider;
|
||||
import net.fabricmc.fabric.api.datagen.v1.provider.SimpleFabricLootTableProvider;
|
||||
import net.minecraft.core.HolderLookup;
|
||||
import net.minecraft.data.CachedOutput;
|
||||
import net.minecraft.data.DataProvider;
|
||||
import net.minecraft.data.PackOutput;
|
||||
import net.minecraft.data.loot.LootTableProvider;
|
||||
import net.minecraft.data.models.BlockModelGenerators;
|
||||
import net.minecraft.data.models.ItemModelGenerators;
|
||||
import net.minecraft.data.tags.TagsProvider;
|
||||
import net.minecraft.resources.ResourceLocation;
|
||||
import net.minecraft.server.packs.PackType;
|
||||
import net.minecraft.tags.TagKey;
|
||||
import net.minecraft.world.item.Item;
|
||||
import net.minecraft.world.level.block.Block;
|
||||
@@ -43,6 +47,27 @@ public class FabricDataGenerators implements DataGeneratorEntrypoint {
|
||||
return generator.addProvider(factory);
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> void addFromCodec(String name, PackType type, String directory, Codec<T> codec, Consumer<BiConsumer<ResourceLocation, T>> output) {
|
||||
generator.addProvider((FabricDataOutput out) -> {
|
||||
var ourType = switch (type) {
|
||||
case SERVER_DATA -> PackOutput.Target.DATA_PACK;
|
||||
case CLIENT_RESOURCES -> PackOutput.Target.RESOURCE_PACK;
|
||||
};
|
||||
return new FabricCodecDataProvider<T>(out, ourType, directory, codec) {
|
||||
@Override
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void configure(BiConsumer<ResourceLocation, T> provider) {
|
||||
output.accept(provider);
|
||||
}
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void lootTable(List<LootTableProvider.SubProviderEntry> tables) {
|
||||
for (var table : tables) {
|
||||
|
||||
Reference in New Issue
Block a user