mirror of
https://github.com/SquidDev-CC/CC-Tweaked
synced 2025-02-11 00:20:05 +00:00
Update to latest Fabric
- Overhaul model loading to work with the new API. This allows for using the emissive texture system in a more generic way, which is nice! - Convert some of our custom models to use Fabric's model hooks (i.e. emitItemQuads). We don't make use of this right now, but might be useful for rendering tools with enchantment glints. Note this does /not/ change any of the turtle block entity rendering code to use Fabric/Forge's model code. This will be a change we want to make in the future. - Some cleanup of our config API. This fixes us printing lots of warnings when creating a new config file on Fabric (same bug also occurs on Forge, but that's a loader problem). - Fix a few warnings
This commit is contained in:
parent
c2988366d8
commit
24d74f5c80
15
README.md
15
README.md
@ -44,7 +44,7 @@ repositories {
|
||||
|
||||
dependencies {
|
||||
// Vanilla (i.e. for multi-loader systems)
|
||||
compileOnly("cc.tweaked:cc-tweaked-$mcVersion-common-api")
|
||||
compileOnly("cc.tweaked:cc-tweaked-$mcVersion-common-api:$cctVersion")
|
||||
|
||||
// Forge Gradle
|
||||
compileOnly("cc.tweaked:cc-tweaked-$mcVersion-core-api:$cctVersion")
|
||||
@ -57,6 +57,19 @@ dependencies {
|
||||
}
|
||||
```
|
||||
|
||||
When using ForgeGradle, you may also need to add the following:
|
||||
|
||||
```groovy
|
||||
minecraft {
|
||||
runs {
|
||||
configureEach {
|
||||
property 'mixin.env.remapRefMap', 'true'
|
||||
property 'mixin.env.refMapRemappingFile', "${buildDir}/createSrgToMcp/output.srg"
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
You should also be careful to only use classes within the `dan200.computercraft.api` package. Non-API classes are
|
||||
subject to change at any point. If you depend on functionality outside the API, file an issue, and we can look into
|
||||
exposing more features.
|
||||
|
@ -7,13 +7,13 @@
|
||||
# Minecraft
|
||||
# MC version is specified in gradle.properties, as we need that in settings.gradle.
|
||||
# Remember to update corresponding versions in fabric.mod.json/mods.toml
|
||||
fabric-api = "0.80.0+1.19.4"
|
||||
fabric-loader = "0.14.19"
|
||||
fabric-api = "0.86.0+1.19.4"
|
||||
fabric-loader = "0.14.21"
|
||||
forge = "45.0.42"
|
||||
forgeSpi = "6.0.0"
|
||||
mixin = "0.8.5"
|
||||
parchment = "2023.03.12"
|
||||
parchmentMc = "1.19.3"
|
||||
parchment = "2023.06.26"
|
||||
parchmentMc = "1.19.4"
|
||||
|
||||
# Normal dependencies
|
||||
asm = "9.3"
|
||||
|
@ -35,6 +35,7 @@ public enum TurtleToolDurability implements StringRepresentable {
|
||||
/**
|
||||
* The codec which may be used for serialising/deserialising {@link TurtleToolDurability}s.
|
||||
*/
|
||||
@SuppressWarnings("deprecation")
|
||||
public static final StringRepresentable.EnumCodec<TurtleToolDurability> CODEC = StringRepresentable.fromEnum(TurtleToolDurability::values);
|
||||
|
||||
TurtleToolDurability(String serialisedName) {
|
||||
|
@ -12,7 +12,6 @@ import dev.emi.emi.api.EmiEntrypoint;
|
||||
import dev.emi.emi.api.EmiPlugin;
|
||||
import dev.emi.emi.api.EmiRegistry;
|
||||
import dev.emi.emi.api.stack.Comparison;
|
||||
import net.minecraft.world.item.Item;
|
||||
import net.minecraft.world.item.ItemStack;
|
||||
|
||||
import java.util.function.BiPredicate;
|
||||
@ -36,7 +35,7 @@ public class EMIComputerCraft implements EmiPlugin {
|
||||
private static final Comparison pocketComparison = compareStacks((left, right) ->
|
||||
left.getItem() instanceof PocketComputerItem && PocketComputerItem.getUpgrade(left) == PocketComputerItem.getUpgrade(right));
|
||||
|
||||
private static <T extends Item> Comparison compareStacks(BiPredicate<ItemStack, ItemStack> test) {
|
||||
private static Comparison compareStacks(BiPredicate<ItemStack, ItemStack> test) {
|
||||
return Comparison.of((left, right) -> {
|
||||
ItemStack leftStack = left.getItemStack(), rightStack = right.getItemStack();
|
||||
return leftStack.getItem() == rightStack.getItem() && test.test(leftStack, rightStack);
|
||||
|
@ -26,13 +26,15 @@ import java.util.List;
|
||||
* <p>
|
||||
* This is typically used with a {@link BakedModel} subclass - see the loader-specific projects.
|
||||
*/
|
||||
public final class ModelTransformer {
|
||||
public static final int[] ORDER = new int[]{ 3, 2, 1, 0 };
|
||||
public class ModelTransformer {
|
||||
@SuppressWarnings("MutablePublicArray") // It's not nice, but is efficient.
|
||||
public static final int[] INVERSE_ORDER = new int[]{ 3, 2, 1, 0 };
|
||||
|
||||
public static final int STRIDE = DefaultVertexFormat.BLOCK.getIntegerSize();
|
||||
private static final int POS_OFFSET = findOffset(DefaultVertexFormat.BLOCK, DefaultVertexFormat.ELEMENT_POSITION);
|
||||
|
||||
private final Matrix4f transformation;
|
||||
private final boolean invert;
|
||||
protected final Matrix4f transformation;
|
||||
protected final boolean invert;
|
||||
private @Nullable TransformedQuads cache;
|
||||
|
||||
public ModelTransformer(Transformation transformation) {
|
||||
@ -60,7 +62,7 @@ public final class ModelTransformer {
|
||||
for (var i = 0; i < 4; i++) {
|
||||
var inStart = STRIDE * i;
|
||||
// Reverse the order of the quads if we're inverting
|
||||
var outStart = STRIDE * (invert ? ORDER[i] : i);
|
||||
var outStart = STRIDE * (invert ? INVERSE_ORDER[i] : i);
|
||||
System.arraycopy(inputData, inStart, outputData, outStart, STRIDE);
|
||||
|
||||
// Apply the matrix to our position
|
||||
|
@ -212,7 +212,7 @@ public class TurtleBlockEntityRenderer implements BlockEntityRenderer<TurtleBloc
|
||||
var normal = matrix.transform(new Vector4f(dirNormal.getX(), dirNormal.getY(), dirNormal.getZ(), 0.0f)).normalize();
|
||||
|
||||
var vertices = quad.getVertices();
|
||||
for (var vertex : ModelTransformer.ORDER) {
|
||||
for (var vertex : ModelTransformer.INVERSE_ORDER) {
|
||||
var i = vertex * ModelTransformer.STRIDE;
|
||||
|
||||
var x = Float.intBitsToFloat(vertices[i]);
|
||||
|
@ -286,8 +286,8 @@ public final class LanguageProvider implements DataProvider {
|
||||
turtleUpgrades.getGeneratedUpgrades().stream().map(UpgradeBase::getUnlocalisedAdjective),
|
||||
pocketUpgrades.getGeneratedUpgrades().stream().map(UpgradeBase::getUnlocalisedAdjective),
|
||||
Metric.metrics().values().stream().map(x -> AggregatedMetric.TRANSLATION_PREFIX + x.name() + ".name"),
|
||||
getConfigEntries(ConfigSpec.serverSpec).map(ConfigFile.Entry::translationKey),
|
||||
getConfigEntries(ConfigSpec.clientSpec).map(ConfigFile.Entry::translationKey)
|
||||
ConfigSpec.serverSpec.entries().map(ConfigFile.Entry::translationKey),
|
||||
ConfigSpec.clientSpec.entries().map(ConfigFile.Entry::translationKey)
|
||||
).flatMap(x -> x);
|
||||
}
|
||||
|
||||
@ -321,16 +321,4 @@ public final class LanguageProvider implements DataProvider {
|
||||
add(value.translationKey(), text);
|
||||
add(value.translationKey() + ".tooltip", value.comment());
|
||||
}
|
||||
|
||||
private static Stream<ConfigFile.Entry> getConfigEntries(ConfigFile spec) {
|
||||
return spec.entries().flatMap(LanguageProvider::getConfigEntries);
|
||||
}
|
||||
|
||||
private static Stream<ConfigFile.Entry> getConfigEntries(ConfigFile.Entry entry) {
|
||||
if (entry instanceof ConfigFile.Value<?>) return Stream.of(entry);
|
||||
if (entry instanceof ConfigFile.Group group) {
|
||||
return Stream.concat(Stream.of(entry), group.children().flatMap(LanguageProvider::getConfigEntries));
|
||||
}
|
||||
throw new IllegalStateException("Invalid config entry " + entry);
|
||||
}
|
||||
}
|
||||
|
@ -54,12 +54,6 @@ public interface ConfigFile {
|
||||
* A group of config entries.
|
||||
*/
|
||||
non-sealed interface Group extends Entry {
|
||||
/**
|
||||
* Get all entries in this group.
|
||||
*
|
||||
* @return All child entries.
|
||||
*/
|
||||
Stream<Entry> children();
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -7,7 +7,6 @@ package dan200.computercraft.shared.util;
|
||||
import javax.annotation.Nullable;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
/**
|
||||
@ -38,12 +37,6 @@ public class Trie<K, V> {
|
||||
getChild(key).current = value;
|
||||
}
|
||||
|
||||
public Stream<V> children() {
|
||||
return children == null
|
||||
? Stream.empty()
|
||||
: children.values().stream().map(x -> x.current).filter(Objects::nonNull);
|
||||
}
|
||||
|
||||
public Stream<V> stream() {
|
||||
return Stream.concat(
|
||||
current == null ? Stream.empty() : Stream.of(current),
|
||||
|
@ -1,16 +1,7 @@
|
||||
{
|
||||
"parent": "minecraft:block/block",
|
||||
"parent": "minecraft:block/orientable",
|
||||
"render_type": "cutout",
|
||||
"textures": {
|
||||
"particle": "#front"
|
||||
},
|
||||
"display": {
|
||||
"firstperson_righthand": {
|
||||
"rotation": [ 0, 135, 0 ],
|
||||
"translation": [ 0, 0, 0 ],
|
||||
"scale": [ 0.40, 0.40, 0.40 ]
|
||||
}
|
||||
},
|
||||
"computercraft:emissive_texture": "cursor",
|
||||
"elements": [
|
||||
{
|
||||
"from": [ 0, 0, 0 ],
|
||||
|
@ -4,6 +4,7 @@
|
||||
|
||||
package dan200.computercraft.core;
|
||||
|
||||
import com.google.common.base.Splitter;
|
||||
import dan200.computercraft.api.filesystem.WritableMount;
|
||||
import dan200.computercraft.api.lua.ILuaAPI;
|
||||
import dan200.computercraft.api.lua.LuaException;
|
||||
@ -346,10 +347,10 @@ public class ComputerTestDelegate {
|
||||
var details = (Map<?, ?>) entry.getValue();
|
||||
var def = (String) details.get("definition");
|
||||
|
||||
var parts = name.split("\0");
|
||||
var parts = Splitter.on('\0').splitToList(name);
|
||||
var builder = root;
|
||||
for (var i = 0; i < parts.length - 1; i++) builder = builder.get(parts[i]);
|
||||
builder.runs(parts[parts.length - 1], def, () -> {
|
||||
for (var i = 0; i < parts.size() - 1; i++) builder = builder.get(parts.get(i));
|
||||
builder.runs(parts.get(parts.size() - 1), def, () -> {
|
||||
// Run it
|
||||
lock.lockInterruptibly();
|
||||
try {
|
||||
|
@ -5,8 +5,7 @@
|
||||
package dan200.computercraft.client;
|
||||
|
||||
import dan200.computercraft.api.ComputerCraftAPI;
|
||||
import dan200.computercraft.client.model.EmissiveComputerModel;
|
||||
import dan200.computercraft.client.model.turtle.TurtleModelLoader;
|
||||
import dan200.computercraft.client.model.CustomModelLoader;
|
||||
import dan200.computercraft.shared.ModRegistry;
|
||||
import dan200.computercraft.shared.config.ConfigSpec;
|
||||
import dan200.computercraft.shared.network.client.ClientNetworkContext;
|
||||
@ -15,7 +14,7 @@ import dan200.computercraft.shared.platform.FabricConfigFile;
|
||||
import dan200.computercraft.shared.platform.NetworkHandler;
|
||||
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.ModelLoadingRegistry;
|
||||
import net.fabricmc.fabric.api.client.model.loading.v1.PreparableModelLoadingPlugin;
|
||||
import net.fabricmc.fabric.api.client.networking.v1.ClientPlayNetworking;
|
||||
import net.fabricmc.fabric.api.client.rendering.v1.ColorProviderRegistry;
|
||||
import net.fabricmc.fabric.api.client.rendering.v1.WorldRenderEvents;
|
||||
@ -40,9 +39,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));
|
||||
PreparableModelLoadingPlugin.register(CustomModelLoader::prepare, (state, context) -> {
|
||||
ClientRegistry.registerExtraModels(context::addModels);
|
||||
context.resolveModel().register(ctx -> state.loadModel(ctx.id()));
|
||||
context.modifyModelAfterBake().register((model, ctx) -> state.wrapModel(ctx, model));
|
||||
});
|
||||
|
||||
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());
|
||||
|
@ -4,24 +4,33 @@
|
||||
|
||||
package dan200.computercraft.client.model;
|
||||
|
||||
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.render.RenderContext;
|
||||
import net.minecraft.client.renderer.block.model.BakedQuad;
|
||||
import net.minecraft.client.resources.model.BakedModel;
|
||||
import net.minecraft.core.BlockPos;
|
||||
import net.minecraft.core.Direction;
|
||||
import net.minecraft.util.RandomSource;
|
||||
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.util.*;
|
||||
import java.util.function.Supplier;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
/**
|
||||
* A {@link BakedModel} formed from two or more other models stitched together.
|
||||
*/
|
||||
public class CompositeBakedModel extends CustomBakedModel {
|
||||
public class CompositeBakedModel extends ForwardingBakedModel {
|
||||
private final boolean isVanillaAdapter;
|
||||
private final List<BakedModel> models;
|
||||
|
||||
public CompositeBakedModel(List<BakedModel> models) {
|
||||
super(models.get(0));
|
||||
wrapped = models.get(0);
|
||||
isVanillaAdapter = models.stream().allMatch(FabricBakedModel::isVanillaAdapter);
|
||||
this.models = models;
|
||||
}
|
||||
|
||||
@ -29,6 +38,11 @@ public class CompositeBakedModel extends CustomBakedModel {
|
||||
return models.size() == 1 ? models.get(0) : new CompositeBakedModel(models);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isVanillaAdapter() {
|
||||
return isVanillaAdapter;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<BakedQuad> getQuads(@Nullable BlockState blockState, @Nullable Direction face, RandomSource rand) {
|
||||
@SuppressWarnings({ "unchecked", "rawtypes" })
|
||||
@ -39,6 +53,16 @@ public class CompositeBakedModel extends CustomBakedModel {
|
||||
return new ConcatListView(quads);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void emitBlockQuads(BlockAndTintGetter blockView, BlockState state, BlockPos pos, Supplier<RandomSource> randomSupplier, RenderContext context) {
|
||||
for (var model : models) model.emitBlockQuads(blockView, state, pos, randomSupplier, context);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void emitItemQuads(ItemStack stack, Supplier<RandomSource> randomSupplier, RenderContext context) {
|
||||
for (var model : models) model.emitItemQuads(stack, randomSupplier, context);
|
||||
}
|
||||
|
||||
private static final class ConcatListView extends AbstractList<BakedQuad> {
|
||||
private final List<BakedQuad>[] quads;
|
||||
|
||||
|
@ -1,42 +0,0 @@
|
||||
// SPDX-FileCopyrightText: 2022 The CC: Tweaked Developers
|
||||
//
|
||||
// SPDX-License-Identifier: MPL-2.0
|
||||
|
||||
package dan200.computercraft.client.model;
|
||||
|
||||
import net.fabricmc.fabric.api.renderer.v1.model.ForwardingBakedModel;
|
||||
import net.fabricmc.fabric.api.renderer.v1.render.RenderContext;
|
||||
import net.minecraft.client.renderer.block.model.BakedQuad;
|
||||
import net.minecraft.client.resources.model.BakedModel;
|
||||
import net.minecraft.core.BlockPos;
|
||||
import net.minecraft.core.Direction;
|
||||
import net.minecraft.util.RandomSource;
|
||||
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.util.List;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
/**
|
||||
* A subclass of {@link ForwardingBakedModel} which doesn't forward rendering.
|
||||
*/
|
||||
public abstract class CustomBakedModel extends ForwardingBakedModel {
|
||||
public CustomBakedModel(BakedModel wrapped) {
|
||||
this.wrapped = wrapped;
|
||||
}
|
||||
|
||||
@Override
|
||||
public abstract List<BakedQuad> getQuads(@Nullable BlockState blockState, @Nullable Direction face, RandomSource rand);
|
||||
|
||||
@Override
|
||||
public final void emitBlockQuads(BlockAndTintGetter blockView, BlockState state, BlockPos pos, Supplier<RandomSource> randomSupplier, RenderContext context) {
|
||||
context.bakedModelConsumer().accept(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public final void emitItemQuads(ItemStack stack, Supplier<RandomSource> randomSupplier, RenderContext context) {
|
||||
context.bakedModelConsumer().accept(this);
|
||||
}
|
||||
}
|
@ -0,0 +1,127 @@
|
||||
// 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.google.gson.JsonParseException;
|
||||
import dan200.computercraft.api.ComputerCraftAPI;
|
||||
import dan200.computercraft.client.model.turtle.UnbakedTurtleModel;
|
||||
import dan200.computercraft.mixin.client.BlockModelAccessor;
|
||||
import net.fabricmc.fabric.api.client.model.loading.v1.ModelModifier;
|
||||
import net.fabricmc.fabric.api.client.model.loading.v1.PreparableModelLoadingPlugin;
|
||||
import net.minecraft.client.renderer.block.model.BlockModel;
|
||||
import net.minecraft.client.resources.model.BakedModel;
|
||||
import net.minecraft.client.resources.model.UnbakedModel;
|
||||
import net.minecraft.resources.FileToIdConverter;
|
||||
import net.minecraft.resources.ResourceLocation;
|
||||
import net.minecraft.server.packs.resources.Resource;
|
||||
import net.minecraft.server.packs.resources.ResourceManager;
|
||||
import net.minecraft.util.GsonHelper;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
import java.io.IOException;
|
||||
import java.io.Reader;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.Executor;
|
||||
|
||||
/**
|
||||
* Provides custom model loading for various CC models.
|
||||
* <p>
|
||||
* This is used from a {@link PreparableModelLoadingPlugin}, which {@linkplain #prepare(ResourceManager, Executor) loads
|
||||
* data from disk} in parallel with other loader plugins, and then hooks into the model loading pipeline
|
||||
* ({@link #loadModel(ResourceLocation)}, {@link #wrapModel(ModelModifier.AfterBake.Context, BakedModel)}).
|
||||
*
|
||||
* @see EmissiveBakedModel
|
||||
* @see UnbakedTurtleModel
|
||||
*/
|
||||
public final class CustomModelLoader {
|
||||
private static final Logger LOG = LoggerFactory.getLogger(CustomModelLoader.class);
|
||||
private static final FileToIdConverter converter = FileToIdConverter.json("models");
|
||||
|
||||
private final Map<ResourceLocation, UnbakedModel> models = new HashMap<>();
|
||||
private final Map<ResourceLocation, String> emissiveModels = new HashMap<>();
|
||||
|
||||
private CustomModelLoader() {
|
||||
}
|
||||
|
||||
public static CompletableFuture<CustomModelLoader> prepare(ResourceManager resources, Executor executor) {
|
||||
return CompletableFuture.supplyAsync(() -> {
|
||||
var loader = new CustomModelLoader();
|
||||
for (var resource : resources.listResources("models", x -> x.getNamespace().equals(ComputerCraftAPI.MOD_ID) && x.getPath().endsWith(".json")).entrySet()) {
|
||||
loader.loadModel(resource.getKey(), resource.getValue());
|
||||
}
|
||||
return loader;
|
||||
}, executor);
|
||||
}
|
||||
|
||||
private void loadModel(ResourceLocation path, Resource resource) {
|
||||
var id = converter.fileToId(path);
|
||||
|
||||
try {
|
||||
JsonObject model;
|
||||
try (Reader reader = resource.openAsReader()) {
|
||||
model = GsonHelper.parse(reader).getAsJsonObject();
|
||||
}
|
||||
|
||||
var loader = GsonHelper.getAsString(model, "loader", null);
|
||||
if (loader != null) {
|
||||
var unbaked = switch (loader) {
|
||||
case ComputerCraftAPI.MOD_ID + ":turtle" -> UnbakedTurtleModel.parse(model);
|
||||
default -> throw new JsonParseException("Unknown model loader " + loader);
|
||||
};
|
||||
models.put(id, unbaked);
|
||||
}
|
||||
|
||||
var emissive = GsonHelper.getAsString(model, "computercraft:emissive_texture", null);
|
||||
if (emissive != null) emissiveModels.put(id, emissive);
|
||||
} catch (IllegalArgumentException | IOException | JsonParseException e) {
|
||||
LOG.error("Couldn't parse model file {} from {}", id, path, e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Load a custom model. This searches for CC models with a custom {@code loader} field.
|
||||
*
|
||||
* @param path The path of the model to load.
|
||||
* @return The unbaked model that has been loaded, or {@code null} if the model should be loaded as a vanilla model.
|
||||
*/
|
||||
public @Nullable UnbakedModel loadModel(ResourceLocation path) {
|
||||
return path.getNamespace().equals(ComputerCraftAPI.MOD_ID) ? models.get(path) : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Wrap a baked model.
|
||||
* <p>
|
||||
* This just finds models which specify an emissive texture ({@code computercraft:emissive_texture} in the JSON) and
|
||||
* wraps them in a {@link EmissiveBakedModel}.
|
||||
*
|
||||
* @param ctx The current model loading context.
|
||||
* @param baked The baked model to wrap.
|
||||
* @return The wrapped model.
|
||||
*/
|
||||
public BakedModel wrapModel(ModelModifier.AfterBake.Context ctx, BakedModel baked) {
|
||||
if (!ctx.id().getNamespace().equals(ComputerCraftAPI.MOD_ID)) return baked;
|
||||
if (!(ctx.sourceModel() instanceof BlockModel model)) return baked;
|
||||
|
||||
var emissive = getEmissive(ctx.id(), model);
|
||||
return emissive == null ? baked : EmissiveBakedModel.wrap(baked, ctx.textureGetter().apply(model.getMaterial(emissive)));
|
||||
}
|
||||
|
||||
private @Nullable String getEmissive(ResourceLocation id, BlockModel model) {
|
||||
while (true) {
|
||||
var emissive = emissiveModels.get(id);
|
||||
if (emissive != null) return emissive;
|
||||
|
||||
id = ((BlockModelAccessor) model).computercraft$getParentLocation();
|
||||
model = ((BlockModelAccessor) model).computercraft$getParent();
|
||||
|
||||
if (id == null || model == null) return null;
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,84 @@
|
||||
// SPDX-FileCopyrightText: 2023 The CC: Tweaked Developers
|
||||
//
|
||||
// SPDX-License-Identifier: MPL-2.0
|
||||
|
||||
package dan200.computercraft.client.model;
|
||||
|
||||
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.ForwardingBakedModel;
|
||||
import net.fabricmc.fabric.api.renderer.v1.model.ModelHelper;
|
||||
import net.fabricmc.fabric.api.renderer.v1.render.RenderContext;
|
||||
import net.minecraft.client.renderer.texture.TextureAtlasSprite;
|
||||
import net.minecraft.client.resources.model.BakedModel;
|
||||
import net.minecraft.core.BlockPos;
|
||||
import net.minecraft.util.RandomSource;
|
||||
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.util.function.Supplier;
|
||||
|
||||
/**
|
||||
* Wraps an arbitrary {@link BakedModel} to render a single texture as emissive.
|
||||
* <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 {@linkplain CustomModelLoader custom model loader} which wraps the
|
||||
* baked model in a {@link EmissiveBakedModel}, which renders specific quads as emissive.
|
||||
*/
|
||||
public final class EmissiveBakedModel extends ForwardingBakedModel {
|
||||
private final TextureAtlasSprite emissiveTexture;
|
||||
private final RenderMaterial defaultMaterial;
|
||||
private final RenderMaterial emissiveMaterial;
|
||||
|
||||
private EmissiveBakedModel(BakedModel wrapped, TextureAtlasSprite emissiveTexture, RenderMaterial defaultMaterial, RenderMaterial emissiveMaterial) {
|
||||
this.wrapped = wrapped;
|
||||
this.emissiveTexture = emissiveTexture;
|
||||
this.defaultMaterial = defaultMaterial;
|
||||
this.emissiveMaterial = emissiveMaterial;
|
||||
}
|
||||
|
||||
public static BakedModel wrap(BakedModel model, TextureAtlasSprite emissiveTexture) {
|
||||
var renderer = RendererAccess.INSTANCE.getRenderer();
|
||||
return renderer == null ? model : new EmissiveBakedModel(
|
||||
model,
|
||||
emissiveTexture,
|
||||
renderer.materialFinder().find(),
|
||||
renderer.materialFinder().emissive(true).find()
|
||||
);
|
||||
}
|
||||
|
||||
@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() == emissiveTexture ? emissiveMaterial : defaultMaterial, cullFace);
|
||||
emitter.emit();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -1,172 +0,0 @@
|
||||
// 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();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,49 @@
|
||||
// SPDX-FileCopyrightText: 2023 The CC: Tweaked Developers
|
||||
//
|
||||
// SPDX-License-Identifier: MPL-2.0
|
||||
|
||||
package dan200.computercraft.client.model;
|
||||
|
||||
import com.mojang.math.Transformation;
|
||||
import dan200.computercraft.client.model.turtle.ModelTransformer;
|
||||
import net.fabricmc.fabric.api.renderer.v1.mesh.MutableQuadView;
|
||||
import net.fabricmc.fabric.api.renderer.v1.render.RenderContext;
|
||||
import net.minecraft.core.Direction;
|
||||
import org.joml.Vector3f;
|
||||
|
||||
/**
|
||||
* Extends {@link ModelTransformer} to also work as a {@link RenderContext.QuadTransform}.
|
||||
*/
|
||||
public class FabricModelTransformer extends ModelTransformer implements RenderContext.QuadTransform {
|
||||
public FabricModelTransformer(Transformation transformation) {
|
||||
super(transformation);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean transform(MutableQuadView quad) {
|
||||
var vec3 = new Vector3f();
|
||||
for (var i = 0; i < 4; i++) {
|
||||
quad.copyPos(i, vec3);
|
||||
transformation.transformPosition(vec3);
|
||||
quad.pos(i, vec3);
|
||||
}
|
||||
|
||||
if (invert) {
|
||||
swapQuads(quad, 0, 3);
|
||||
swapQuads(quad, 1, 2);
|
||||
}
|
||||
|
||||
var face = quad.nominalFace();
|
||||
if (face != null) quad.nominalFace(Direction.rotate(transformation, face));
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private static void swapQuads(MutableQuadView quad, int a, int b) {
|
||||
float aX = quad.x(a), aY = quad.y(a), aZ = quad.z(a), aU = quad.u(a), aV = quad.v(a);
|
||||
float bX = quad.x(b), bY = quad.y(b), bZ = quad.z(b), bU = quad.u(b), bV = quad.v(b);
|
||||
|
||||
quad.pos(b, aX, aY, aZ).uv(b, aU, aV);
|
||||
quad.pos(a, bX, bY, bZ).uv(a, bU, bV);
|
||||
}
|
||||
}
|
@ -6,30 +6,49 @@ package dan200.computercraft.client.model;
|
||||
|
||||
import com.mojang.math.Transformation;
|
||||
import dan200.computercraft.client.model.turtle.ModelTransformer;
|
||||
import net.fabricmc.fabric.api.renderer.v1.model.ForwardingBakedModel;
|
||||
import net.fabricmc.fabric.api.renderer.v1.render.RenderContext;
|
||||
import net.minecraft.client.renderer.block.model.BakedQuad;
|
||||
import net.minecraft.client.resources.model.BakedModel;
|
||||
import net.minecraft.core.BlockPos;
|
||||
import net.minecraft.core.Direction;
|
||||
import net.minecraft.util.RandomSource;
|
||||
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.util.List;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
/**
|
||||
* A {@link BakedModel} which applies a transformation matrix to its underlying quads.
|
||||
*
|
||||
* @see ModelTransformer
|
||||
*/
|
||||
public class TransformedBakedModel extends CustomBakedModel {
|
||||
private final ModelTransformer transformation;
|
||||
public class TransformedBakedModel extends ForwardingBakedModel {
|
||||
private final FabricModelTransformer transformation;
|
||||
|
||||
public TransformedBakedModel(BakedModel model, Transformation transformation) {
|
||||
super(model);
|
||||
this.transformation = new ModelTransformer(transformation);
|
||||
wrapped = model;
|
||||
this.transformation = new FabricModelTransformer(transformation);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<BakedQuad> getQuads(@Nullable BlockState blockState, @Nullable Direction face, RandomSource rand) {
|
||||
return transformation.transform(wrapped.getQuads(blockState, face, rand));
|
||||
}
|
||||
|
||||
@Override
|
||||
public final void emitBlockQuads(BlockAndTintGetter blockView, BlockState state, BlockPos pos, Supplier<RandomSource> randomSupplier, RenderContext context) {
|
||||
super.emitBlockQuads(blockView, state, pos, randomSupplier, context);
|
||||
context.popTransform();
|
||||
}
|
||||
|
||||
@Override
|
||||
public final void emitItemQuads(ItemStack stack, Supplier<RandomSource> randomSupplier, RenderContext context) {
|
||||
context.pushTransform(transformation);
|
||||
super.emitItemQuads(stack, randomSupplier, context);
|
||||
context.popTransform();
|
||||
}
|
||||
}
|
||||
|
@ -1,82 +0,0 @@
|
||||
// SPDX-FileCopyrightText: 2022 The CC: Tweaked Developers
|
||||
//
|
||||
// SPDX-License-Identifier: MPL-2.0
|
||||
|
||||
package dan200.computercraft.client.model.turtle;
|
||||
|
||||
import dan200.computercraft.api.ComputerCraftAPI;
|
||||
import net.fabricmc.fabric.api.client.model.ModelProviderException;
|
||||
import net.minecraft.client.renderer.texture.TextureAtlasSprite;
|
||||
import net.minecraft.client.resources.model.*;
|
||||
import net.minecraft.resources.ResourceLocation;
|
||||
import net.minecraft.server.packs.resources.ResourceManager;
|
||||
import net.minecraft.util.GsonHelper;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
import java.io.IOException;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.function.Function;
|
||||
|
||||
/**
|
||||
* A model "loader" (the concept doesn't quite exist in the same way as it does on Forge) for turtle item models.
|
||||
* <p>
|
||||
* This reads in the associated model file (typically {@code computercraft:block/turtle_xxx}) and wraps it in a
|
||||
* {@link TurtleModel}.
|
||||
*/
|
||||
public final class TurtleModelLoader {
|
||||
private static final ResourceLocation COLOUR_TURTLE_MODEL = new ResourceLocation(ComputerCraftAPI.MOD_ID, "block/turtle_colour");
|
||||
|
||||
private TurtleModelLoader() {
|
||||
}
|
||||
|
||||
public static @Nullable UnbakedModel load(ResourceManager resources, ResourceLocation path) throws ModelProviderException {
|
||||
if (!path.getNamespace().equals(ComputerCraftAPI.MOD_ID)) return null;
|
||||
if (!path.getPath().equals("item/turtle_normal") && !path.getPath().equals("item/turtle_advanced")) {
|
||||
return null;
|
||||
}
|
||||
|
||||
try (var reader = resources.openAsReader(new ResourceLocation(path.getNamespace(), "models/" + path.getPath() + ".json"))) {
|
||||
var modelContents = GsonHelper.parse(reader).getAsJsonObject();
|
||||
|
||||
var loader = GsonHelper.getAsString(modelContents, "loader", null);
|
||||
if (!Objects.equals(loader, ComputerCraftAPI.MOD_ID + ":turtle")) return null;
|
||||
|
||||
var model = new ResourceLocation(GsonHelper.getAsString(modelContents, "model"));
|
||||
return new Unbaked(model);
|
||||
} catch (IOException e) {
|
||||
throw new ModelProviderException("Failed loading model " + path, e);
|
||||
}
|
||||
}
|
||||
|
||||
public static final class Unbaked implements UnbakedModel {
|
||||
private final ResourceLocation model;
|
||||
|
||||
private Unbaked(ResourceLocation model) {
|
||||
this.model = model;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<ResourceLocation> getDependencies() {
|
||||
return List.of(model, COLOUR_TURTLE_MODEL);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void resolveParents(Function<ResourceLocation, UnbakedModel> function) {
|
||||
function.apply(model).resolveParents(function);
|
||||
function.apply(COLOUR_TURTLE_MODEL).resolveParents(function);
|
||||
}
|
||||
|
||||
@Override
|
||||
public BakedModel bake(ModelBaker bakery, Function<Material, TextureAtlasSprite> spriteGetter, ModelState transform, ResourceLocation location) {
|
||||
var mainModel = bakery.bake(model, transform);
|
||||
if (mainModel == null) throw new NullPointerException(model + " failed to bake");
|
||||
|
||||
var colourModel = bakery.bake(COLOUR_TURTLE_MODEL, transform);
|
||||
if (colourModel == null) throw new NullPointerException(COLOUR_TURTLE_MODEL + " failed to bake");
|
||||
|
||||
return new TurtleModel(mainModel, colourModel);
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,59 @@
|
||||
// SPDX-FileCopyrightText: 2022 The CC: Tweaked Developers
|
||||
//
|
||||
// SPDX-License-Identifier: MPL-2.0
|
||||
|
||||
package dan200.computercraft.client.model.turtle;
|
||||
|
||||
import com.google.gson.JsonObject;
|
||||
import dan200.computercraft.api.ComputerCraftAPI;
|
||||
import net.minecraft.client.renderer.texture.TextureAtlasSprite;
|
||||
import net.minecraft.client.resources.model.*;
|
||||
import net.minecraft.resources.ResourceLocation;
|
||||
import net.minecraft.util.GsonHelper;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.function.Function;
|
||||
|
||||
/**
|
||||
* A {@link UnbakedModel} for {@link TurtleModel}s.
|
||||
* <p>
|
||||
* This reads in the associated model file (typically {@code computercraft:block/turtle_xxx}) and wraps it in a
|
||||
* {@link TurtleModel}.
|
||||
*/
|
||||
public final class UnbakedTurtleModel implements UnbakedModel {
|
||||
private static final ResourceLocation COLOUR_TURTLE_MODEL = new ResourceLocation(ComputerCraftAPI.MOD_ID, "block/turtle_colour");
|
||||
|
||||
private final ResourceLocation model;
|
||||
|
||||
private UnbakedTurtleModel(ResourceLocation model) {
|
||||
this.model = model;
|
||||
}
|
||||
|
||||
public static UnbakedModel parse(JsonObject json) {
|
||||
var model = new ResourceLocation(GsonHelper.getAsString(json, "model"));
|
||||
return new UnbakedTurtleModel(model);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<ResourceLocation> getDependencies() {
|
||||
return List.of(model, COLOUR_TURTLE_MODEL);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void resolveParents(Function<ResourceLocation, UnbakedModel> function) {
|
||||
function.apply(model).resolveParents(function);
|
||||
function.apply(COLOUR_TURTLE_MODEL).resolveParents(function);
|
||||
}
|
||||
|
||||
@Override
|
||||
public BakedModel bake(ModelBaker bakery, Function<Material, TextureAtlasSprite> spriteGetter, ModelState transform, ResourceLocation location) {
|
||||
var mainModel = bakery.bake(model, transform);
|
||||
if (mainModel == null) throw new NullPointerException(model + " failed to bake");
|
||||
|
||||
var colourModel = bakery.bake(COLOUR_TURTLE_MODEL, transform);
|
||||
if (colourModel == null) throw new NullPointerException(COLOUR_TURTLE_MODEL + " failed to bake");
|
||||
|
||||
return new TurtleModel(mainModel, colourModel);
|
||||
}
|
||||
}
|
@ -8,7 +8,6 @@ import com.google.auto.service.AutoService;
|
||||
import dan200.computercraft.shared.network.NetworkMessage;
|
||||
import dan200.computercraft.shared.network.server.ServerNetworkContext;
|
||||
import dan200.computercraft.shared.platform.NetworkHandler;
|
||||
import net.fabricmc.fabric.api.client.model.BakedModelManagerHelper;
|
||||
import net.minecraft.client.Minecraft;
|
||||
import net.minecraft.client.resources.model.BakedModel;
|
||||
import net.minecraft.client.resources.model.ModelManager;
|
||||
@ -23,7 +22,7 @@ public class ClientPlatformHelperImpl implements ClientPlatformHelper {
|
||||
|
||||
@Override
|
||||
public BakedModel getModel(ModelManager manager, ResourceLocation location) {
|
||||
var model = BakedModelManagerHelper.getModel(manager, location);
|
||||
var model = manager.getModel(location);
|
||||
return model == null ? manager.getMissingModel() : model;
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,23 @@
|
||||
// SPDX-FileCopyrightText: 2023 The CC: Tweaked Developers
|
||||
//
|
||||
// SPDX-License-Identifier: MPL-2.0
|
||||
|
||||
package dan200.computercraft.mixin.client;
|
||||
|
||||
import net.minecraft.client.renderer.block.model.BlockModel;
|
||||
import net.minecraft.resources.ResourceLocation;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.gen.Accessor;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
@Mixin(BlockModel.class)
|
||||
public interface BlockModelAccessor {
|
||||
@Accessor("parentLocation")
|
||||
@Nullable
|
||||
ResourceLocation computercraft$getParentLocation();
|
||||
|
||||
@Accessor("parent")
|
||||
@Nullable
|
||||
BlockModel computercraft$getParent();
|
||||
}
|
@ -7,6 +7,7 @@
|
||||
"defaultRequire": 1
|
||||
},
|
||||
"client": [
|
||||
"BlockModelAccessor",
|
||||
"BlockRenderDispatcherMixin",
|
||||
"DebugScreenOverlayMixin",
|
||||
"GameRendererMixin",
|
||||
|
@ -73,7 +73,7 @@ public class FabricDataGenerators implements DataGeneratorEntrypoint {
|
||||
for (var table : tables) {
|
||||
generator.addProvider((FabricDataOutput out) -> new SimpleFabricLootTableProvider(out, table.paramSet()) {
|
||||
@Override
|
||||
public void accept(BiConsumer<ResourceLocation, LootTable.Builder> exporter) {
|
||||
public void generate(BiConsumer<ResourceLocation, LootTable.Builder> exporter) {
|
||||
table.provider().get().generate(exporter);
|
||||
}
|
||||
});
|
||||
|
@ -62,12 +62,15 @@ public class FabricConfigFile implements ConfigFile {
|
||||
if (loadConfig()) config.save();
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private Stream<ValueImpl<?>> values() {
|
||||
return (Stream<ValueImpl<?>>) (Stream<?>) entries.stream().filter(ValueImpl.class::isInstance);
|
||||
}
|
||||
|
||||
public synchronized void unload() {
|
||||
closeConfig();
|
||||
|
||||
entries.stream().forEach(x -> {
|
||||
if (x instanceof ValueImpl<?> value) value.unload();
|
||||
});
|
||||
values().forEach(ValueImpl::unload);
|
||||
}
|
||||
|
||||
@GuardedBy("this")
|
||||
@ -87,14 +90,15 @@ public class FabricConfigFile implements ConfigFile {
|
||||
|
||||
config.load();
|
||||
|
||||
entries.stream().forEach(x -> {
|
||||
config.setComment(x.path, x.comment);
|
||||
if (x instanceof ValueImpl<?> value) value.load(config);
|
||||
});
|
||||
var corrected = spec.correct(config, (action, entryPath, oldValue, newValue) -> {
|
||||
// Ensure the config file matches the spec
|
||||
var isNewFile = config.isEmpty();
|
||||
entries.stream().forEach(x -> config.setComment(x.path, x.comment));
|
||||
var corrected = isNewFile ? spec.correct(config) : spec.correct(config, (action, entryPath, oldValue, newValue) -> {
|
||||
LOG.warn("Incorrect key {} was corrected from {} to {}", String.join(".", entryPath), oldValue, newValue);
|
||||
});
|
||||
|
||||
// And then load the underlying entries.
|
||||
values().forEach(x -> x.load(config));
|
||||
onChange.onConfigChanged(config.getNioPath());
|
||||
|
||||
return corrected > 0;
|
||||
@ -102,7 +106,7 @@ public class FabricConfigFile implements ConfigFile {
|
||||
|
||||
@Override
|
||||
public Stream<ConfigFile.Entry> entries() {
|
||||
return entries.children().map(x -> (ConfigFile.Entry) x);
|
||||
return entries.stream().map(x -> (ConfigFile.Entry) x);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@ -148,7 +152,7 @@ public class FabricConfigFile implements ConfigFile {
|
||||
public void push(String name) {
|
||||
var path = getFullPath(name);
|
||||
var splitPath = SPLITTER.split(path);
|
||||
entries.setValue(splitPath, new GroupImpl(path, takeComment(), entries.getChild(splitPath)));
|
||||
entries.setValue(splitPath, new GroupImpl(path, takeComment()));
|
||||
|
||||
super.push(name);
|
||||
}
|
||||
@ -230,16 +234,8 @@ public class FabricConfigFile implements ConfigFile {
|
||||
}
|
||||
|
||||
private static final class GroupImpl extends Entry implements Group {
|
||||
private final Trie<String, Entry> children;
|
||||
|
||||
private GroupImpl(String path, String comment, Trie<String, Entry> children) {
|
||||
private GroupImpl(String path, String comment) {
|
||||
super(path, comment);
|
||||
this.children = children;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Stream<ConfigFile.Entry> children() {
|
||||
return children.children().map(x -> (ConfigFile.Entry) x);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -49,8 +49,8 @@
|
||||
}
|
||||
],
|
||||
"depends": {
|
||||
"fabricloader": ">=0.14.17",
|
||||
"fabric-api": ">=0.80.0",
|
||||
"fabricloader": ">=0.14.21",
|
||||
"fabric-api": ">=0.86.0",
|
||||
"minecraft": ">=1.19.4 <1.20"
|
||||
},
|
||||
"accessWidener": "computercraft.accesswidener"
|
||||
|
@ -32,7 +32,7 @@ public final class ForgeConfigFile implements ConfigFile {
|
||||
|
||||
@Override
|
||||
public Stream<Entry> entries() {
|
||||
return entries.children();
|
||||
return entries.stream();
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@ -68,7 +68,7 @@ public final class ForgeConfigFile implements ConfigFile {
|
||||
@Override
|
||||
public void pop() {
|
||||
var path = new ArrayList<>(groupStack);
|
||||
entries.setValue(path, new GroupImpl(path, entries.getChild(path)));
|
||||
entries.setValue(path, new GroupImpl(path));
|
||||
|
||||
builder.pop();
|
||||
super.pop();
|
||||
@ -129,12 +129,10 @@ public final class ForgeConfigFile implements ConfigFile {
|
||||
|
||||
private static final class GroupImpl implements ConfigFile.Group {
|
||||
private final List<String> path;
|
||||
private final Trie<String, ConfigFile.Entry> entries;
|
||||
private @Nullable ForgeConfigSpec owner;
|
||||
|
||||
private GroupImpl(List<String> path, Trie<String, ConfigFile.Entry> entries) {
|
||||
private GroupImpl(List<String> path) {
|
||||
this.path = path;
|
||||
this.entries = entries;
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -148,11 +146,6 @@ public final class ForgeConfigFile implements ConfigFile {
|
||||
if (owner == null) throw new IllegalStateException("Config has not been built yet");
|
||||
return owner.getLevelComment(path);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Stream<Entry> children() {
|
||||
return entries.children();
|
||||
}
|
||||
}
|
||||
|
||||
private static final class ValueImpl<T> implements ConfigFile.Value<T> {
|
||||
|
Loading…
x
Reference in New Issue
Block a user