mirror of
https://github.com/SquidDev-CC/CC-Tweaked
synced 2025-11-02 22:53:15 +00:00
Merge branch 'mc-1.19.x' into mc-1.20.x
This commit is contained in:
@@ -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) -> model == null ? null : 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);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,79 @@
|
||||
// SPDX-FileCopyrightText: 2023 The CC: Tweaked Developers
|
||||
//
|
||||
// SPDX-License-Identifier: MPL-2.0
|
||||
|
||||
package dan200.computercraft.client.model;
|
||||
|
||||
import dan200.computercraft.core.util.Nullability;
|
||||
import net.fabricmc.fabric.api.renderer.v1.Renderer;
|
||||
import net.fabricmc.fabric.api.renderer.v1.RendererAccess;
|
||||
import net.fabricmc.fabric.api.renderer.v1.material.RenderMaterial;
|
||||
import net.fabricmc.fabric.api.renderer.v1.mesh.MutableQuadView;
|
||||
import net.fabricmc.fabric.api.renderer.v1.model.ForwardingBakedModel;
|
||||
import net.fabricmc.fabric.api.renderer.v1.render.RenderContext;
|
||||
import net.fabricmc.fabric.api.util.TriState;
|
||||
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;
|
||||
|
||||
/**
|
||||
* A model wrapper which applies a {@link RenderMaterial#glint() glint}/foil to the original model.
|
||||
*/
|
||||
public final class FoiledModel extends ForwardingBakedModel implements RenderContext.QuadTransform {
|
||||
private final @Nullable Renderer renderer = RendererAccess.INSTANCE.getRenderer();
|
||||
private @Nullable RenderMaterial lastMaterial, lastFoiledMaterial;
|
||||
|
||||
public FoiledModel(BakedModel model) {
|
||||
wrapped = model;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isVanillaAdapter() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void emitBlockQuads(BlockAndTintGetter blockView, BlockState state, BlockPos pos, Supplier<RandomSource> randomSupplier, RenderContext context) {
|
||||
context.pushTransform(this);
|
||||
super.emitBlockQuads(blockView, state, pos, randomSupplier, context);
|
||||
context.popTransform();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void emitItemQuads(ItemStack stack, Supplier<RandomSource> randomSupplier, RenderContext context) {
|
||||
context.pushTransform(this);
|
||||
super.emitItemQuads(stack, randomSupplier, context);
|
||||
context.popTransform();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
return this == obj || (obj instanceof FoiledModel other && wrapped.equals(other.wrapped));
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return wrapped.hashCode() ^ 1;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean transform(MutableQuadView quad) {
|
||||
if (renderer == null) return true;
|
||||
|
||||
var material = quad.material();
|
||||
if (material == lastMaterial) {
|
||||
quad.material(Nullability.assertNonNull(lastFoiledMaterial));
|
||||
} else {
|
||||
lastMaterial = material;
|
||||
quad.material(lastFoiledMaterial = renderer.materialFinder().copyFrom(material).glint(TriState.TRUE).find());
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -5,17 +5,28 @@
|
||||
package dan200.computercraft.client.platform;
|
||||
|
||||
import com.google.auto.service.AutoService;
|
||||
import com.mojang.blaze3d.vertex.PoseStack;
|
||||
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 net.fabricmc.fabric.api.client.model.BakedModelManagerHelper;
|
||||
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;
|
||||
import net.minecraft.client.resources.model.BakedModel;
|
||||
import net.minecraft.client.resources.model.ModelManager;
|
||||
import net.minecraft.resources.ResourceLocation;
|
||||
import net.minecraft.util.RandomSource;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
@AutoService(dan200.computercraft.impl.client.ClientPlatformHelper.class)
|
||||
public class ClientPlatformHelperImpl implements ClientPlatformHelper {
|
||||
private static final RandomSource random = RandomSource.create(0);
|
||||
|
||||
@Override
|
||||
public void sendToServer(NetworkMessage<ServerNetworkContext> message) {
|
||||
Minecraft.getInstance().player.connection.send(NetworkHandler.encodeServer(message));
|
||||
@@ -23,7 +34,25 @@ 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;
|
||||
}
|
||||
|
||||
@Override
|
||||
public BakedModel createdFoiledModel(BakedModel model) {
|
||||
return new FoiledModel(model);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void renderBakedModel(PoseStack transform, MultiBufferSource buffers, BakedModel model, int lightmapCoord, int overlayLight, @Nullable int[] 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 = ItemRenderer.getFoilBuffer(buffers, Sheets.translucentCullBlockSheet(), true, model instanceof FoiledModel);
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -41,9 +41,6 @@
|
||||
"commands.computercraft.help.synopsis": "Provide help for a specific command",
|
||||
"commands.computercraft.queue.desc": "Send a computer_command event to a command computer, passing through the additional arguments. This is mostly designed for map makers, acting as a more computer-friendly version of /trigger. Any player can run the command, which would most likely be done through a text component's click event.",
|
||||
"commands.computercraft.queue.synopsis": "Send a computer_command event to a command computer",
|
||||
"commands.computercraft.reload.desc": "Reload the ComputerCraft config file",
|
||||
"commands.computercraft.reload.done": "Reloaded config",
|
||||
"commands.computercraft.reload.synopsis": "Reload the ComputerCraft config file",
|
||||
"commands.computercraft.shutdown.desc": "Shutdown the listed computers or all if none are specified. You can specify the computer's instance id (e.g. 123), computer id (e.g #123) or label (e.g. \"@My Computer\").",
|
||||
"commands.computercraft.shutdown.done": "Shutdown %s/%s computers",
|
||||
"commands.computercraft.shutdown.synopsis": "Shutdown computers remotely.",
|
||||
|
||||
@@ -4,6 +4,9 @@
|
||||
"group": "computercraft:turtle_advanced",
|
||||
"key": {"#": {"item": "minecraft:diamond_axe"}, "T": {"item": "computercraft:turtle_advanced"}},
|
||||
"pattern": ["#T"],
|
||||
"result": {"item": "computercraft:turtle_advanced", "nbt": "{RightUpgrade:\"minecraft:diamond_axe\"}"},
|
||||
"result": {
|
||||
"item": "computercraft:turtle_advanced",
|
||||
"nbt": "{RightUpgrade:\"minecraft:diamond_axe\",RightUpgradeNbt:{Tag:{Damage:0}}}"
|
||||
},
|
||||
"show_notification": true
|
||||
}
|
||||
|
||||
@@ -4,6 +4,9 @@
|
||||
"group": "computercraft:turtle_advanced",
|
||||
"key": {"#": {"item": "minecraft:diamond_hoe"}, "T": {"item": "computercraft:turtle_advanced"}},
|
||||
"pattern": ["#T"],
|
||||
"result": {"item": "computercraft:turtle_advanced", "nbt": "{RightUpgrade:\"minecraft:diamond_hoe\"}"},
|
||||
"result": {
|
||||
"item": "computercraft:turtle_advanced",
|
||||
"nbt": "{RightUpgrade:\"minecraft:diamond_hoe\",RightUpgradeNbt:{Tag:{Damage:0}}}"
|
||||
},
|
||||
"show_notification": true
|
||||
}
|
||||
|
||||
@@ -4,6 +4,9 @@
|
||||
"group": "computercraft:turtle_advanced",
|
||||
"key": {"#": {"item": "minecraft:diamond_pickaxe"}, "T": {"item": "computercraft:turtle_advanced"}},
|
||||
"pattern": ["#T"],
|
||||
"result": {"item": "computercraft:turtle_advanced", "nbt": "{RightUpgrade:\"minecraft:diamond_pickaxe\"}"},
|
||||
"result": {
|
||||
"item": "computercraft:turtle_advanced",
|
||||
"nbt": "{RightUpgrade:\"minecraft:diamond_pickaxe\",RightUpgradeNbt:{Tag:{Damage:0}}}"
|
||||
},
|
||||
"show_notification": true
|
||||
}
|
||||
|
||||
@@ -4,6 +4,9 @@
|
||||
"group": "computercraft:turtle_advanced",
|
||||
"key": {"#": {"item": "minecraft:diamond_shovel"}, "T": {"item": "computercraft:turtle_advanced"}},
|
||||
"pattern": ["#T"],
|
||||
"result": {"item": "computercraft:turtle_advanced", "nbt": "{RightUpgrade:\"minecraft:diamond_shovel\"}"},
|
||||
"result": {
|
||||
"item": "computercraft:turtle_advanced",
|
||||
"nbt": "{RightUpgrade:\"minecraft:diamond_shovel\",RightUpgradeNbt:{Tag:{Damage:0}}}"
|
||||
},
|
||||
"show_notification": true
|
||||
}
|
||||
|
||||
@@ -4,6 +4,9 @@
|
||||
"group": "computercraft:turtle_advanced",
|
||||
"key": {"#": {"item": "minecraft:diamond_sword"}, "T": {"item": "computercraft:turtle_advanced"}},
|
||||
"pattern": ["#T"],
|
||||
"result": {"item": "computercraft:turtle_advanced", "nbt": "{RightUpgrade:\"minecraft:diamond_sword\"}"},
|
||||
"result": {
|
||||
"item": "computercraft:turtle_advanced",
|
||||
"nbt": "{RightUpgrade:\"minecraft:diamond_sword\",RightUpgradeNbt:{Tag:{Damage:0}}}"
|
||||
},
|
||||
"show_notification": true
|
||||
}
|
||||
|
||||
@@ -4,6 +4,9 @@
|
||||
"group": "computercraft:turtle_normal",
|
||||
"key": {"#": {"item": "minecraft:diamond_axe"}, "T": {"item": "computercraft:turtle_normal"}},
|
||||
"pattern": ["#T"],
|
||||
"result": {"item": "computercraft:turtle_normal", "nbt": "{RightUpgrade:\"minecraft:diamond_axe\"}"},
|
||||
"result": {
|
||||
"item": "computercraft:turtle_normal",
|
||||
"nbt": "{RightUpgrade:\"minecraft:diamond_axe\",RightUpgradeNbt:{Tag:{Damage:0}}}"
|
||||
},
|
||||
"show_notification": true
|
||||
}
|
||||
|
||||
@@ -4,6 +4,9 @@
|
||||
"group": "computercraft:turtle_normal",
|
||||
"key": {"#": {"item": "minecraft:diamond_hoe"}, "T": {"item": "computercraft:turtle_normal"}},
|
||||
"pattern": ["#T"],
|
||||
"result": {"item": "computercraft:turtle_normal", "nbt": "{RightUpgrade:\"minecraft:diamond_hoe\"}"},
|
||||
"result": {
|
||||
"item": "computercraft:turtle_normal",
|
||||
"nbt": "{RightUpgrade:\"minecraft:diamond_hoe\",RightUpgradeNbt:{Tag:{Damage:0}}}"
|
||||
},
|
||||
"show_notification": true
|
||||
}
|
||||
|
||||
@@ -4,6 +4,9 @@
|
||||
"group": "computercraft:turtle_normal",
|
||||
"key": {"#": {"item": "minecraft:diamond_pickaxe"}, "T": {"item": "computercraft:turtle_normal"}},
|
||||
"pattern": ["#T"],
|
||||
"result": {"item": "computercraft:turtle_normal", "nbt": "{RightUpgrade:\"minecraft:diamond_pickaxe\"}"},
|
||||
"result": {
|
||||
"item": "computercraft:turtle_normal",
|
||||
"nbt": "{RightUpgrade:\"minecraft:diamond_pickaxe\",RightUpgradeNbt:{Tag:{Damage:0}}}"
|
||||
},
|
||||
"show_notification": true
|
||||
}
|
||||
|
||||
@@ -4,6 +4,9 @@
|
||||
"group": "computercraft:turtle_normal",
|
||||
"key": {"#": {"item": "minecraft:diamond_shovel"}, "T": {"item": "computercraft:turtle_normal"}},
|
||||
"pattern": ["#T"],
|
||||
"result": {"item": "computercraft:turtle_normal", "nbt": "{RightUpgrade:\"minecraft:diamond_shovel\"}"},
|
||||
"result": {
|
||||
"item": "computercraft:turtle_normal",
|
||||
"nbt": "{RightUpgrade:\"minecraft:diamond_shovel\",RightUpgradeNbt:{Tag:{Damage:0}}}"
|
||||
},
|
||||
"show_notification": true
|
||||
}
|
||||
|
||||
@@ -4,6 +4,9 @@
|
||||
"group": "computercraft:turtle_normal",
|
||||
"key": {"#": {"item": "minecraft:diamond_sword"}, "T": {"item": "computercraft:turtle_normal"}},
|
||||
"pattern": ["#T"],
|
||||
"result": {"item": "computercraft:turtle_normal", "nbt": "{RightUpgrade:\"minecraft:diamond_sword\"}"},
|
||||
"result": {
|
||||
"item": "computercraft:turtle_normal",
|
||||
"nbt": "{RightUpgrade:\"minecraft:diamond_sword\",RightUpgradeNbt:{Tag:{Damage:0}}}"
|
||||
},
|
||||
"show_notification": true
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -309,7 +309,7 @@ public class PlatformHelperImpl implements PlatformHelper {
|
||||
public boolean hasToolUsage(ItemStack stack) {
|
||||
var item = stack.getItem();
|
||||
return item instanceof ShovelItem || stack.is(ItemTags.SHOVELS) ||
|
||||
item instanceof HoeItem || stack.is(ItemTags.HOES);
|
||||
item instanceof HoeItem || stack.is(ItemTags.HOES);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -343,9 +343,7 @@ public class PlatformHelperImpl implements PlatformHelper {
|
||||
) implements RegistryWrappers.RegistryWrapper<T> {
|
||||
@Override
|
||||
public int getId(T object) {
|
||||
var id = registry.getId(object);
|
||||
if (id == -1) throw new IllegalArgumentException(object + " was not registered in " + name);
|
||||
return id;
|
||||
return registry.getId(object);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -369,10 +367,13 @@ public class PlatformHelperImpl implements PlatformHelper {
|
||||
}
|
||||
|
||||
@Override
|
||||
public T get(int id) {
|
||||
var object = registry.byId(id);
|
||||
if (object == null) throw new IllegalArgumentException(id + " was not registered in " + name);
|
||||
return object;
|
||||
public @Nullable T byId(int id) {
|
||||
return registry.byId(id);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int size() {
|
||||
return registry.size();
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -50,7 +50,7 @@
|
||||
],
|
||||
"depends": {
|
||||
"fabricloader": ">=0.14.21",
|
||||
"fabric-api": ">=0.83.0",
|
||||
"fabric-api": ">=0.86.1",
|
||||
"minecraft": ">=1.20.1 <1.21"
|
||||
},
|
||||
"accessWidener": "computercraft.accesswidener"
|
||||
|
||||
Reference in New Issue
Block a user