mirror of
https://github.com/SquidDev-CC/CC-Tweaked
synced 2025-05-23 09:44:11 +00:00
Load turtle overlays during model loading
Back in f10e401aeac0b141b230607f6c3347215f0093cc, we changed turtle overlays to be loaded as a dynamic registry. This solved some of the problems we had with upgrades (elf-compatibility), and seemed like a good idea at the time. However, because overlays are part of datapacks (not resource packs), we also needed support for loading overlay models, which we did via an "extra_models.json" file. With the ItemModel changes, we can go for a different approach: - Turtle overlays are now stored on the item/BE as a simple ResourceLocation again. - The TurtleOverlay class is moved to the client, and loaded from resource packs, during model loading. They're now split into a baked form (holding a StandaloneModel) and an unbaked one (holding the ResourceLocation). - extra_model.json is no longer supported.
This commit is contained in:
parent
e13e8ff92e
commit
598fd98a8b
@ -20,10 +20,11 @@ import dan200.computercraft.client.render.CustomLecternRenderer;
|
||||
import dan200.computercraft.client.render.TurtleBlockEntityRenderer;
|
||||
import dan200.computercraft.client.render.monitor.MonitorBlockEntityRenderer;
|
||||
import dan200.computercraft.client.turtle.TurtleModemModel;
|
||||
import dan200.computercraft.client.turtle.TurtleOverlay;
|
||||
import dan200.computercraft.client.turtle.TurtleOverlayManager;
|
||||
import dan200.computercraft.client.turtle.TurtleUpgradeModels;
|
||||
import dan200.computercraft.shared.ModRegistry;
|
||||
import dan200.computercraft.shared.computer.inventory.AbstractComputerMenu;
|
||||
import dan200.computercraft.shared.turtle.TurtleOverlay;
|
||||
import net.minecraft.client.Minecraft;
|
||||
import net.minecraft.client.color.item.ItemTintSource;
|
||||
import net.minecraft.client.gui.screens.MenuScreens;
|
||||
@ -35,17 +36,22 @@ import net.minecraft.client.renderer.item.ItemModel;
|
||||
import net.minecraft.client.renderer.item.properties.conditional.ConditionalItemModelProperty;
|
||||
import net.minecraft.client.renderer.item.properties.select.SelectItemModelProperty;
|
||||
import net.minecraft.client.resources.model.MissingBlockModel;
|
||||
import net.minecraft.client.resources.model.ModelBaker;
|
||||
import net.minecraft.client.resources.model.ModelManager;
|
||||
import net.minecraft.client.resources.model.ResolvableModel;
|
||||
import net.minecraft.resources.ResourceLocation;
|
||||
import net.minecraft.server.packs.resources.PreparableReloadListener;
|
||||
import net.minecraft.server.packs.resources.ResourceManager;
|
||||
import net.minecraft.world.inventory.AbstractContainerMenu;
|
||||
import net.minecraft.world.inventory.MenuType;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.Executor;
|
||||
import java.util.function.BiConsumer;
|
||||
import java.util.function.BiFunction;
|
||||
|
||||
/**
|
||||
* Registers client-side objects, such as {@link BlockEntityRendererProvider}s and
|
||||
@ -122,14 +128,61 @@ public final class ClientRegistry {
|
||||
MissingBlockModel.LOCATION,
|
||||
};
|
||||
|
||||
public static void registerExtraModels(
|
||||
BiConsumer<ModelKey<StandaloneModel>, ResourceLocation> registerBasic,
|
||||
BiConsumer<ModelKey<? extends TurtleUpgradeModel<?>>, TurtleUpgradeModel.Unbaked<?>> registerTurtle,
|
||||
Collection<ResourceLocation> extraModels
|
||||
/**
|
||||
* Additional models to load.
|
||||
*
|
||||
* @param turtleOverlays The unbaked turtle models.
|
||||
* @see #gatherExtraModels(ResourceManager, Executor)
|
||||
* @see #registerExtraModels(RegisterExtraModels, ExtraModels)
|
||||
*/
|
||||
public record ExtraModels(
|
||||
Map<ResourceLocation, TurtleOverlay.Unbaked> turtleOverlays
|
||||
) {
|
||||
for (var model : EXTRA_MODELS) registerBasic.accept(getModel(model), model);
|
||||
for (var model : extraModels) registerBasic.accept(getModel(model), model);
|
||||
TurtleUpgradeModels.bake(registerTurtle);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gather the list of extra models to load.
|
||||
*
|
||||
* @param resources The current resource manager.
|
||||
* @param executor The executor to schedule loading on.
|
||||
* @return A promise which contains our extra models.
|
||||
*/
|
||||
public static CompletableFuture<ExtraModels> gatherExtraModels(ResourceManager resources, Executor executor) {
|
||||
var turtleOverlays = TurtleOverlayManager.load(resources, executor);
|
||||
return turtleOverlays.thenApply(ExtraModels::new);
|
||||
}
|
||||
|
||||
/**
|
||||
* A callback used to register a model for a {@link ModelKey}.
|
||||
*/
|
||||
public interface RegisterExtraModels {
|
||||
default <U extends ResolvableModel, T> void register(ModelKey<T> key, U unbaked, BiFunction<U, ModelBaker, T> bake) {
|
||||
register(key, unbaked, ResolvableModel::resolveDependencies, bake);
|
||||
}
|
||||
|
||||
/**
|
||||
* Register an extra model.
|
||||
* <p>
|
||||
* This accepts functions to resolve dependencies and bake the model. While this would be conceptually nicer as
|
||||
* an interface, it would require multiple adaptors to convert between "upgrade model", "a"bstract model" and
|
||||
* "platform-specific model", so working with functions is cleaner.
|
||||
*
|
||||
* @param key The model key for this model.
|
||||
* @param unbaked The unbaked model.
|
||||
* @param resolve The function to resolve dependencies for this model.
|
||||
* @param bake The function to bake this model.
|
||||
* @param <U> The type of unbaked model.
|
||||
* @param <T> The type of baked model.
|
||||
*/
|
||||
<U, T> void register(ModelKey<T> key, U unbaked, BiConsumer<U, ResolvableModel.Resolver> resolve, BiFunction<U, ModelBaker, T> bake);
|
||||
}
|
||||
|
||||
public static void registerExtraModels(RegisterExtraModels register, ExtraModels models) {
|
||||
for (var model : EXTRA_MODELS) {
|
||||
register.register(getModel(model), model, (id, r) -> r.markDependency(id), StandaloneModel::of);
|
||||
}
|
||||
TurtleOverlayManager.register(register, models.turtleOverlays());
|
||||
TurtleUpgradeModels.bake(register);
|
||||
}
|
||||
|
||||
public static void registerItemModels(BiConsumer<ResourceLocation, MapCodec<? extends ItemModel.Unbaked>> register) {
|
||||
|
@ -7,8 +7,8 @@ package dan200.computercraft.client.item.model;
|
||||
import com.mojang.serialization.MapCodec;
|
||||
import com.mojang.serialization.codecs.RecordCodecBuilder;
|
||||
import dan200.computercraft.api.ComputerCraftAPI;
|
||||
import dan200.computercraft.client.ClientRegistry;
|
||||
import dan200.computercraft.shared.turtle.TurtleOverlay;
|
||||
import dan200.computercraft.client.turtle.TurtleOverlay;
|
||||
import dan200.computercraft.client.turtle.TurtleOverlayManager;
|
||||
import dan200.computercraft.shared.turtle.items.TurtleItem;
|
||||
import net.minecraft.client.Minecraft;
|
||||
import net.minecraft.client.multiplayer.ClientLevel;
|
||||
@ -40,7 +40,7 @@ public record TurtleOverlayModel(ItemTransforms transforms) implements ItemModel
|
||||
if (overlay == null) return;
|
||||
|
||||
var layer = state.newLayer();
|
||||
ClientRegistry.getModel(Minecraft.getInstance().getModelManager(), overlay.model()).setupItemLayer(layer);
|
||||
TurtleOverlayManager.getOverlay(Minecraft.getInstance().getModelManager(), overlay).model().setupItemLayer(layer);
|
||||
layer.setTransform(transforms().getTransform(context));
|
||||
}
|
||||
|
||||
|
@ -6,8 +6,10 @@ package dan200.computercraft.client.item.properties;
|
||||
|
||||
import com.mojang.serialization.MapCodec;
|
||||
import dan200.computercraft.api.ComputerCraftAPI;
|
||||
import dan200.computercraft.shared.turtle.TurtleOverlay;
|
||||
import dan200.computercraft.client.turtle.TurtleOverlay;
|
||||
import dan200.computercraft.client.turtle.TurtleOverlayManager;
|
||||
import dan200.computercraft.shared.turtle.items.TurtleItem;
|
||||
import net.minecraft.client.Minecraft;
|
||||
import net.minecraft.client.multiplayer.ClientLevel;
|
||||
import net.minecraft.client.renderer.item.properties.conditional.ConditionalItemModelProperty;
|
||||
import net.minecraft.resources.ResourceLocation;
|
||||
@ -29,7 +31,7 @@ public class TurtleShowElfOverlay implements ConditionalItemModelProperty {
|
||||
|
||||
@Override
|
||||
public boolean get(ItemStack stack, @Nullable ClientLevel level, @Nullable LivingEntity holder, int i, ItemDisplayContext context) {
|
||||
var overlay = TurtleItem.getOverlay(stack);
|
||||
var overlay = TurtleOverlayManager.getOverlay(Minecraft.getInstance().getModelManager(), TurtleItem.getOverlay(stack));
|
||||
return overlay == null || overlay.showElfOverlay();
|
||||
}
|
||||
|
||||
|
@ -1,68 +0,0 @@
|
||||
// SPDX-FileCopyrightText: 2024 The CC: Tweaked Developers
|
||||
//
|
||||
// SPDX-License-Identifier: MPL-2.0
|
||||
|
||||
package dan200.computercraft.client.model;
|
||||
|
||||
import com.google.gson.Gson;
|
||||
import com.google.gson.JsonElement;
|
||||
import com.google.gson.JsonParseException;
|
||||
import com.mojang.serialization.Codec;
|
||||
import com.mojang.serialization.JsonOps;
|
||||
import dan200.computercraft.api.ComputerCraftAPI;
|
||||
import dan200.computercraft.shared.turtle.TurtleOverlay;
|
||||
import net.minecraft.resources.ResourceLocation;
|
||||
import net.minecraft.server.packs.resources.ResourceManager;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.*;
|
||||
|
||||
/**
|
||||
* A list of extra models to load on the client.
|
||||
* <p>
|
||||
* This is largely intended for use with {@linkplain TurtleOverlay turtle overlays}. As overlays are stored in a dynamic
|
||||
* registry, they are not available when resources are loaded, and so we need a way to request the overlays' models be
|
||||
* loaded.
|
||||
*
|
||||
* @param models The models to load.
|
||||
*/
|
||||
public record ExtraModels(List<ResourceLocation> models) {
|
||||
private static final Logger LOG = LoggerFactory.getLogger(ExtraModels.class);
|
||||
private static final Gson GSON = new Gson();
|
||||
|
||||
/**
|
||||
* The path where the extra models are listed.
|
||||
*/
|
||||
public static final ResourceLocation PATH = ResourceLocation.fromNamespaceAndPath(ComputerCraftAPI.MOD_ID, "extra_models.json");
|
||||
|
||||
/**
|
||||
* The coded used to store the extra model file.
|
||||
*/
|
||||
public static final Codec<ExtraModels> CODEC = ResourceLocation.CODEC.listOf().xmap(ExtraModels::new, ExtraModels::models);
|
||||
|
||||
/**
|
||||
* Get the list of all extra models to load.
|
||||
*
|
||||
* @param resources The current resource manager.
|
||||
* @return A set of all resources to load.
|
||||
*/
|
||||
public static Collection<ResourceLocation> loadAll(ResourceManager resources) {
|
||||
Set<ResourceLocation> out = new HashSet<>();
|
||||
|
||||
for (var path : resources.getResourceStack(PATH)) {
|
||||
ExtraModels models;
|
||||
try (var stream = path.openAsReader()) {
|
||||
models = ExtraModels.CODEC.parse(JsonOps.INSTANCE, GSON.fromJson(stream, JsonElement.class)).getOrThrow(JsonParseException::new);
|
||||
} catch (IOException | RuntimeException e) {
|
||||
LOG.error("Failed to load extra models from {}", path.sourcePackId());
|
||||
continue;
|
||||
}
|
||||
|
||||
out.addAll(models.models());
|
||||
}
|
||||
|
||||
return Collections.unmodifiableCollection(out);
|
||||
}
|
||||
}
|
@ -9,9 +9,10 @@ import com.mojang.math.Axis;
|
||||
import dan200.computercraft.api.ComputerCraftAPI;
|
||||
import dan200.computercraft.api.turtle.TurtleSide;
|
||||
import dan200.computercraft.client.ClientRegistry;
|
||||
import dan200.computercraft.client.turtle.TurtleOverlay;
|
||||
import dan200.computercraft.client.turtle.TurtleOverlayManager;
|
||||
import dan200.computercraft.client.turtle.TurtleUpgradeModels;
|
||||
import dan200.computercraft.shared.computer.core.ComputerFamily;
|
||||
import dan200.computercraft.shared.turtle.TurtleOverlay;
|
||||
import dan200.computercraft.shared.turtle.blocks.TurtleBlockEntity;
|
||||
import dan200.computercraft.shared.util.Holiday;
|
||||
import net.minecraft.client.Minecraft;
|
||||
@ -78,7 +79,7 @@ public class TurtleBlockEntityRenderer implements BlockEntityRenderer<TurtleBloc
|
||||
|
||||
// Render the turtle
|
||||
var colour = turtle.getColour();
|
||||
var overlay = turtle.getOverlay();
|
||||
var overlay = TurtleOverlayManager.getOverlay(Minecraft.getInstance().getModelManager(), turtle.getOverlay());
|
||||
|
||||
if (colour == -1) {
|
||||
renderModel(transform, buffers, lightmapCoord, overlayLight, turtle.getFamily() == ComputerFamily.NORMAL ? NORMAL_TURTLE_MODEL : ADVANCED_TURTLE_MODEL, null);
|
||||
@ -88,10 +89,10 @@ public class TurtleBlockEntityRenderer implements BlockEntityRenderer<TurtleBloc
|
||||
}
|
||||
|
||||
// Render the overlay
|
||||
if (overlay != null) renderModel(transform, buffers, lightmapCoord, overlayLight, overlay.model(), null);
|
||||
if (overlay != null) overlay.model().render(transform, buffers, lightmapCoord, overlayLight);
|
||||
|
||||
// And the Christmas overlay.
|
||||
var showChristmas = TurtleOverlay.showElfOverlay(overlay, Holiday.getCurrent() == Holiday.CHRISTMAS);
|
||||
var showChristmas = Holiday.getCurrent() == Holiday.CHRISTMAS && (overlay == null || overlay.showElfOverlay());
|
||||
if (showChristmas) renderModel(transform, buffers, lightmapCoord, overlayLight, TurtleOverlay.ELF_MODEL, null);
|
||||
|
||||
// Render the upgrades
|
||||
|
@ -0,0 +1,55 @@
|
||||
// SPDX-FileCopyrightText: 2024 The CC: Tweaked Developers
|
||||
//
|
||||
// SPDX-License-Identifier: MPL-2.0
|
||||
|
||||
package dan200.computercraft.client.turtle;
|
||||
|
||||
import com.mojang.serialization.Codec;
|
||||
import com.mojang.serialization.codecs.RecordCodecBuilder;
|
||||
import dan200.computercraft.api.ComputerCraftAPI;
|
||||
import dan200.computercraft.api.client.StandaloneModel;
|
||||
import dan200.computercraft.shared.ModRegistry;
|
||||
import dan200.computercraft.shared.util.Holiday;
|
||||
import net.minecraft.client.resources.model.ModelBaker;
|
||||
import net.minecraft.client.resources.model.ResolvableModel;
|
||||
import net.minecraft.resources.ResourceLocation;
|
||||
|
||||
/**
|
||||
* A cosmetic overlay on a turtle.
|
||||
*
|
||||
* @param model The path to the overlay's model.
|
||||
* @param showElfOverlay Whether this overlay is compatible with the {@linkplain #ELF_MODEL Christmas elf model}.
|
||||
* @see ModRegistry.DataComponents#OVERLAY
|
||||
*/
|
||||
public record TurtleOverlay(StandaloneModel model, boolean showElfOverlay) {
|
||||
/**
|
||||
* The folder where upgrades are loaded from.
|
||||
*/
|
||||
public static final String SOURCE = ComputerCraftAPI.MOD_ID + "/turtle_overlay";
|
||||
|
||||
/**
|
||||
* The codec used to read/write turtle overlay definitions from resource packs.
|
||||
*/
|
||||
public static final Codec<TurtleOverlay.Unbaked> CODEC = RecordCodecBuilder.create(instance -> instance.group(
|
||||
ResourceLocation.CODEC.fieldOf("model").forGetter(TurtleOverlay.Unbaked::model),
|
||||
Codec.BOOL.optionalFieldOf("show_elf_overlay", false).forGetter(TurtleOverlay.Unbaked::showElfOverlay)
|
||||
).apply(instance, TurtleOverlay.Unbaked::new));
|
||||
|
||||
/**
|
||||
* An additional overlay that is rendered on all turtles at {@linkplain Holiday#CHRISTMAS Christmas}.
|
||||
*
|
||||
* @see #showElfOverlay()
|
||||
*/
|
||||
public static final ResourceLocation ELF_MODEL = ResourceLocation.fromNamespaceAndPath(ComputerCraftAPI.MOD_ID, "block/turtle_elf_overlay");
|
||||
|
||||
public record Unbaked(ResourceLocation model, boolean showElfOverlay) implements ResolvableModel {
|
||||
@Override
|
||||
public void resolveDependencies(Resolver resolver) {
|
||||
resolver.markDependency(model());
|
||||
}
|
||||
|
||||
public TurtleOverlay bake(ModelBaker baker) {
|
||||
return new TurtleOverlay(StandaloneModel.of(model(), baker), showElfOverlay());
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,88 @@
|
||||
// SPDX-FileCopyrightText: 2025 The CC: Tweaked Developers
|
||||
//
|
||||
// SPDX-License-Identifier: MPL-2.0
|
||||
|
||||
package dan200.computercraft.client.turtle;
|
||||
|
||||
import com.mojang.serialization.JsonOps;
|
||||
import dan200.computercraft.client.ClientRegistry;
|
||||
import dan200.computercraft.client.platform.ClientPlatformHelper;
|
||||
import dan200.computercraft.client.platform.ModelKey;
|
||||
import dan200.computercraft.shared.util.ResourceUtils;
|
||||
import net.minecraft.client.resources.model.MissingBlockModel;
|
||||
import net.minecraft.client.resources.model.ModelManager;
|
||||
import net.minecraft.resources.FileToIdConverter;
|
||||
import net.minecraft.resources.ResourceLocation;
|
||||
import net.minecraft.server.packs.resources.ResourceManager;
|
||||
import org.jetbrains.annotations.Contract;
|
||||
import org.jspecify.annotations.Nullable;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.Executor;
|
||||
|
||||
/**
|
||||
* A manager for loading {@link TurtleOverlay}s. This is responsible for {@linkplain #load(ResourceManager, Executor)
|
||||
* loading overlays from resource packs}, {@linkplain #register(ClientRegistry.RegisterExtraModels, Map) baking them}, and
|
||||
* then {@linkplain #getOverlay(ModelManager, ResourceLocation) looking them up}.
|
||||
*/
|
||||
public class TurtleOverlayManager {
|
||||
private static final FileToIdConverter ID_CONVERTER = FileToIdConverter.json(TurtleOverlay.SOURCE);
|
||||
|
||||
/**
|
||||
* The {@link ModelKey} for the missing turtle overlay. This is used by
|
||||
* {@link #getOverlay(ModelManager, ResourceLocation)} when an overlay does not exist.
|
||||
*/
|
||||
private static final ModelKey<TurtleOverlay> MISSING_KEY = ClientPlatformHelper.get().createModelKey(
|
||||
MissingBlockModel.LOCATION, () -> "Missing turtle overlay"
|
||||
);
|
||||
|
||||
private static final Map<ResourceLocation, ModelKey<TurtleOverlay>> modelKeys = new ConcurrentHashMap<>();
|
||||
|
||||
private static ModelKey<TurtleOverlay> getModelKey(ResourceLocation overlay) {
|
||||
return modelKeys.computeIfAbsent(overlay, o -> ClientPlatformHelper.get().createModelKey(o, () -> "Turtle overlay " + o));
|
||||
}
|
||||
|
||||
/**
|
||||
* Load our overlays from resources.
|
||||
*
|
||||
* @param resources The current resource manager.
|
||||
* @param executor The executor to schedule work on.
|
||||
* @return The map of unbaked overlay.
|
||||
*/
|
||||
public static CompletableFuture<Map<ResourceLocation, TurtleOverlay.Unbaked>> load(ResourceManager resources, Executor executor) {
|
||||
return ResourceUtils.load(resources, executor, "turtle overlay", ID_CONVERTER, JsonOps.INSTANCE, TurtleOverlay.CODEC);
|
||||
}
|
||||
|
||||
/**
|
||||
* Register our unbaked overlay models.
|
||||
*
|
||||
* @param register The callback to register models with.
|
||||
* @param overlays The overlays to register.
|
||||
*/
|
||||
public static void register(ClientRegistry.RegisterExtraModels register, Map<ResourceLocation, TurtleOverlay.Unbaked> overlays) {
|
||||
overlays.forEach((id, overlay) -> register.register(getModelKey(id), overlay, TurtleOverlay.Unbaked::bake));
|
||||
register.register(MISSING_KEY, new TurtleOverlay.Unbaked(MissingBlockModel.LOCATION, false), TurtleOverlay.Unbaked::bake);
|
||||
}
|
||||
|
||||
/**
|
||||
* Find the turtle overlay with the given id. If the overlay does not exist, then the "missing model" overlay is
|
||||
* returned instead.
|
||||
*
|
||||
* @param modelManager The model manager.
|
||||
* @param id The overlay id.
|
||||
* @return The turtle overlay.
|
||||
*/
|
||||
@Contract("_, null -> null; _, !null -> !null")
|
||||
public static @Nullable TurtleOverlay getOverlay(ModelManager modelManager, @Nullable ResourceLocation id) {
|
||||
if (id == null) return null;
|
||||
|
||||
var overlay = getModelKey(id).get(modelManager);
|
||||
if (overlay != null) return overlay;
|
||||
|
||||
var missing = MISSING_KEY.get(modelManager);
|
||||
if (missing == null) throw new IllegalStateException("Rendering turtles before models are baked");
|
||||
return missing;
|
||||
}
|
||||
}
|
@ -7,6 +7,7 @@ package dan200.computercraft.client.turtle;
|
||||
import dan200.computercraft.api.client.turtle.TurtleUpgradeModel;
|
||||
import dan200.computercraft.api.turtle.ITurtleUpgrade;
|
||||
import dan200.computercraft.api.upgrades.UpgradeType;
|
||||
import dan200.computercraft.client.ClientRegistry;
|
||||
import dan200.computercraft.client.platform.ClientPlatformHelper;
|
||||
import dan200.computercraft.client.platform.ModelKey;
|
||||
import dan200.computercraft.shared.util.RegistryHelper;
|
||||
@ -15,7 +16,6 @@ import net.minecraft.client.resources.model.MissingBlockModel;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.function.BiConsumer;
|
||||
|
||||
/**
|
||||
* A registry of {@link TurtleUpgradeModel}s.
|
||||
@ -79,8 +79,10 @@ public final class TurtleUpgradeModels {
|
||||
return missing;
|
||||
}
|
||||
|
||||
public static void bake(BiConsumer<ModelKey<? extends TurtleUpgradeModel<?>>, TurtleUpgradeModel.Unbaked<?>> baker) {
|
||||
unbaked.forEach(baker);
|
||||
baker.accept(missingModelKey, TurtleUpgradeModel.sided(MissingBlockModel.LOCATION, MissingBlockModel.LOCATION));
|
||||
@SuppressWarnings("unchecked")
|
||||
public static void bake(ClientRegistry.RegisterExtraModels register) {
|
||||
unbaked.forEach((id, model) ->
|
||||
register.register((ModelKey<TurtleUpgradeModel<?>>) id, model, TurtleUpgradeModel.Unbaked::bake));
|
||||
register.register(missingModelKey, TurtleUpgradeModel.sided(MissingBlockModel.LOCATION, MissingBlockModel.LOCATION), TurtleUpgradeModel.Unbaked::bake);
|
||||
}
|
||||
}
|
||||
|
@ -10,10 +10,9 @@ import dan200.computercraft.api.turtle.ITurtleUpgrade;
|
||||
import dan200.computercraft.client.gui.GuiSprites;
|
||||
import dan200.computercraft.client.model.LecternPocketModel;
|
||||
import dan200.computercraft.client.model.LecternPrintoutModel;
|
||||
import dan200.computercraft.client.turtle.TurtleOverlay;
|
||||
import dan200.computercraft.data.client.BlockModelProvider;
|
||||
import dan200.computercraft.data.client.ExtraModelsProvider;
|
||||
import dan200.computercraft.data.client.ItemModelProvider;
|
||||
import dan200.computercraft.shared.turtle.TurtleOverlay;
|
||||
import dan200.computercraft.shared.turtle.inventory.UpgradeSlot;
|
||||
import net.minecraft.Util;
|
||||
import net.minecraft.client.data.models.BlockModelGenerators;
|
||||
@ -55,7 +54,6 @@ public final class DataProviders {
|
||||
Util.make(new RegistrySetBuilder(), builder -> {
|
||||
builder.add(ITurtleUpgrade.REGISTRY, TurtleUpgradeProvider::addUpgrades);
|
||||
builder.add(IPocketUpgrade.REGISTRY, PocketUpgradeProvider::addUpgrades);
|
||||
builder.add(TurtleOverlay.REGISTRY, TurtleOverlays::register);
|
||||
}));
|
||||
var fullRegistries = fullRegistryPatch.thenApply(RegistrySetBuilder.PatchedRegistries::full);
|
||||
|
||||
@ -90,12 +88,7 @@ public final class DataProviders {
|
||||
));
|
||||
});
|
||||
|
||||
generator.add(pack -> new ExtraModelsProvider(pack, fullRegistries) {
|
||||
@Override
|
||||
public Stream<ResourceLocation> getModels(HolderLookup.Provider registries) {
|
||||
return registries.lookupOrThrow(TurtleOverlay.REGISTRY).listElements().map(x -> x.value().model());
|
||||
}
|
||||
});
|
||||
generator.addFromCodec("Turtle overlays", PackOutput.Target.RESOURCE_PACK, TurtleOverlay.SOURCE, TurtleOverlay.CODEC, TurtleOverlays::register);
|
||||
|
||||
generator.addModels(BlockModelProvider::addBlockModels, ItemModelProvider::addItemModels);
|
||||
}
|
||||
|
@ -24,7 +24,6 @@ import dan200.computercraft.shared.recipe.ImpostorShapedRecipe;
|
||||
import dan200.computercraft.shared.recipe.TransformShapedRecipe;
|
||||
import dan200.computercraft.shared.recipe.TransformShapelessRecipe;
|
||||
import dan200.computercraft.shared.recipe.function.CopyComponents;
|
||||
import dan200.computercraft.shared.turtle.TurtleOverlay;
|
||||
import dan200.computercraft.shared.turtle.items.TurtleItem;
|
||||
import dan200.computercraft.shared.turtle.recipes.TurtleUpgradeRecipe;
|
||||
import dan200.computercraft.shared.util.ColourUtils;
|
||||
@ -178,13 +177,11 @@ final class RecipeProvider extends net.minecraft.data.recipes.RecipeProvider {
|
||||
);
|
||||
}
|
||||
|
||||
private void turtleOverlay(ResourceKey<TurtleOverlay> overlay, Consumer<ShapelessSpecBuilder> build) {
|
||||
var holder = registries.lookupOrThrow(overlay.registryKey()).getOrThrow(overlay);
|
||||
|
||||
private void turtleOverlay(ResourceLocation overlay, Consumer<ShapelessSpecBuilder> build) {
|
||||
for (var turtleItem : turtleItems()) {
|
||||
var name = RegistryHelper.getKeyOrThrow(BuiltInRegistries.ITEM, turtleItem);
|
||||
|
||||
var builder = customShapeless(RecipeCategory.REDSTONE, DataComponentUtil.createStack(turtleItem, ModRegistry.DataComponents.OVERLAY.get(), holder))
|
||||
var builder = customShapeless(RecipeCategory.REDSTONE, DataComponentUtil.createStack(turtleItem, ModRegistry.DataComponents.OVERLAY.get(), overlay))
|
||||
.group(name.withSuffix("_overlay").toString())
|
||||
.unlockedBy("has_turtle", has(turtleItem));
|
||||
build.accept(builder);
|
||||
@ -193,7 +190,7 @@ final class RecipeProvider extends net.minecraft.data.recipes.RecipeProvider {
|
||||
.build(s -> new TransformShapelessRecipe(s, List.of(
|
||||
CopyComponents.builder(turtleItem).exclude(ModRegistry.DataComponents.OVERLAY.get()).build()
|
||||
)))
|
||||
.save(output, name.withSuffix("_overlays/" + overlay.location().getPath()));
|
||||
.save(output, name.withSuffix("_overlays/" + overlay.getPath()));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -5,32 +5,32 @@
|
||||
package dan200.computercraft.data;
|
||||
|
||||
import dan200.computercraft.api.ComputerCraftAPI;
|
||||
import dan200.computercraft.shared.turtle.TurtleOverlay;
|
||||
import net.minecraft.data.worldgen.BootstrapContext;
|
||||
import net.minecraft.resources.ResourceKey;
|
||||
import dan200.computercraft.client.turtle.TurtleOverlay;
|
||||
import net.minecraft.resources.ResourceLocation;
|
||||
|
||||
import java.util.function.BiConsumer;
|
||||
|
||||
/**
|
||||
* Built-in turtle overlays.
|
||||
*/
|
||||
final class TurtleOverlays {
|
||||
public static final ResourceKey<TurtleOverlay> RAINBOW_FLAG = create("rainbow_flag");
|
||||
public static final ResourceKey<TurtleOverlay> TRANS_FLAG = create("trans_flag");
|
||||
public static final ResourceLocation RAINBOW_FLAG = create("rainbow_flag");
|
||||
public static final ResourceLocation TRANS_FLAG = create("trans_flag");
|
||||
|
||||
private static ResourceKey<TurtleOverlay> create(String name) {
|
||||
return ResourceKey.create(TurtleOverlay.REGISTRY, ResourceLocation.fromNamespaceAndPath(ComputerCraftAPI.MOD_ID, name));
|
||||
private static ResourceLocation create(String name) {
|
||||
return ResourceLocation.fromNamespaceAndPath(ComputerCraftAPI.MOD_ID, name);
|
||||
}
|
||||
|
||||
private TurtleOverlays() {
|
||||
}
|
||||
|
||||
public static void register(BootstrapContext<TurtleOverlay> registry) {
|
||||
registry.register(RAINBOW_FLAG, new TurtleOverlay(
|
||||
public static void register(BiConsumer<ResourceLocation, TurtleOverlay.Unbaked> registry) {
|
||||
registry.accept(RAINBOW_FLAG, new TurtleOverlay.Unbaked(
|
||||
ResourceLocation.fromNamespaceAndPath(ComputerCraftAPI.MOD_ID, "block/turtle_rainbow_overlay"),
|
||||
true
|
||||
));
|
||||
|
||||
registry.register(TRANS_FLAG, new TurtleOverlay(
|
||||
registry.accept(TRANS_FLAG, new TurtleOverlay.Unbaked(
|
||||
ResourceLocation.fromNamespaceAndPath(ComputerCraftAPI.MOD_ID, "block/turtle_trans_overlay"),
|
||||
true
|
||||
));
|
||||
|
@ -11,6 +11,7 @@ import dan200.computercraft.client.item.model.TurtleOverlayModel;
|
||||
import dan200.computercraft.client.item.model.TurtleUpgradeModel;
|
||||
import dan200.computercraft.client.item.properties.TurtleShowElfOverlay;
|
||||
import dan200.computercraft.client.render.TurtleBlockEntityRenderer;
|
||||
import dan200.computercraft.client.turtle.TurtleOverlay;
|
||||
import dan200.computercraft.shared.ModRegistry;
|
||||
import dan200.computercraft.shared.computer.blocks.ComputerBlock;
|
||||
import dan200.computercraft.shared.peripheral.diskdrive.DiskDriveBlock;
|
||||
@ -21,7 +22,6 @@ import dan200.computercraft.shared.peripheral.modem.wireless.WirelessModemBlock;
|
||||
import dan200.computercraft.shared.peripheral.monitor.MonitorBlock;
|
||||
import dan200.computercraft.shared.peripheral.monitor.MonitorEdgeState;
|
||||
import dan200.computercraft.shared.peripheral.printer.PrinterBlock;
|
||||
import dan200.computercraft.shared.turtle.TurtleOverlay;
|
||||
import dan200.computercraft.shared.turtle.blocks.TurtleBlock;
|
||||
import dan200.computercraft.shared.util.DirectionUtil;
|
||||
import net.minecraft.client.data.models.BlockModelGenerators;
|
||||
|
@ -1,52 +0,0 @@
|
||||
// SPDX-FileCopyrightText: 2024 The CC: Tweaked Developers
|
||||
//
|
||||
// SPDX-License-Identifier: MPL-2.0
|
||||
|
||||
package dan200.computercraft.data.client;
|
||||
|
||||
import com.mojang.serialization.JsonOps;
|
||||
import dan200.computercraft.client.model.ExtraModels;
|
||||
import net.minecraft.core.HolderLookup;
|
||||
import net.minecraft.data.CachedOutput;
|
||||
import net.minecraft.data.DataProvider;
|
||||
import net.minecraft.data.PackOutput;
|
||||
import net.minecraft.resources.ResourceLocation;
|
||||
|
||||
import java.nio.file.Path;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
/**
|
||||
* A data provider to generate {@link ExtraModels}.
|
||||
*/
|
||||
public abstract class ExtraModelsProvider implements DataProvider {
|
||||
private final Path path;
|
||||
private final CompletableFuture<HolderLookup.Provider> registries;
|
||||
|
||||
public ExtraModelsProvider(PackOutput output, CompletableFuture<HolderLookup.Provider> registries) {
|
||||
path = output.getOutputFolder(PackOutput.Target.RESOURCE_PACK).resolve(ExtraModels.PATH.getNamespace()).resolve(ExtraModels.PATH.getPath());
|
||||
this.registries = registries;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a stream of models to load.
|
||||
*
|
||||
* @param registries The current registries.
|
||||
* @return The collection of extra models to load.
|
||||
*/
|
||||
public abstract Stream<ResourceLocation> getModels(HolderLookup.Provider registries);
|
||||
|
||||
@Override
|
||||
public final CompletableFuture<?> run(CachedOutput output) {
|
||||
return registries.thenCompose(registries -> {
|
||||
var models = new ExtraModels(getModels(registries).sorted().toList());
|
||||
var json = ExtraModels.CODEC.encodeStart(JsonOps.INSTANCE, models).getOrThrow(IllegalStateException::new);
|
||||
return DataProvider.saveStable(output, json, path);
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public final String getName() {
|
||||
return "Extra Models";
|
||||
}
|
||||
}
|
@ -1 +0,0 @@
|
||||
["computercraft:block/turtle_rainbow_overlay", "computercraft:block/turtle_trans_overlay"]
|
@ -81,7 +81,6 @@ import dan200.computercraft.shared.recipe.*;
|
||||
import dan200.computercraft.shared.recipe.function.CopyComponents;
|
||||
import dan200.computercraft.shared.recipe.function.RecipeFunction;
|
||||
import dan200.computercraft.shared.turtle.FurnaceRefuelHandler;
|
||||
import dan200.computercraft.shared.turtle.TurtleOverlay;
|
||||
import dan200.computercraft.shared.turtle.apis.TurtleAPI;
|
||||
import dan200.computercraft.shared.turtle.blocks.TurtleBlock;
|
||||
import dan200.computercraft.shared.turtle.blocks.TurtleBlockEntity;
|
||||
@ -389,8 +388,8 @@ public final class ModRegistry {
|
||||
/**
|
||||
* The overlay on a turtle.
|
||||
*/
|
||||
public static final RegistryEntry<DataComponentType<Holder<TurtleOverlay>>> OVERLAY = register("overlay", b -> b
|
||||
.persistent(TurtleOverlay.CODEC).networkSynchronized(TurtleOverlay.STREAM_CODEC)
|
||||
public static final RegistryEntry<DataComponentType<ResourceLocation>> OVERLAY = register("overlay", b -> b
|
||||
.persistent(ResourceLocation.CODEC).networkSynchronized(ResourceLocation.STREAM_CODEC).cacheEncoding()
|
||||
);
|
||||
|
||||
/**
|
||||
|
@ -1,77 +0,0 @@
|
||||
// SPDX-FileCopyrightText: 2024 The CC: Tweaked Developers
|
||||
//
|
||||
// SPDX-License-Identifier: MPL-2.0
|
||||
|
||||
package dan200.computercraft.shared.turtle;
|
||||
|
||||
import com.mojang.serialization.Codec;
|
||||
import com.mojang.serialization.codecs.RecordCodecBuilder;
|
||||
import dan200.computercraft.api.ComputerCraftAPI;
|
||||
import dan200.computercraft.shared.ModRegistry;
|
||||
import dan200.computercraft.shared.util.Holiday;
|
||||
import net.minecraft.core.Holder;
|
||||
import net.minecraft.core.Registry;
|
||||
import net.minecraft.network.RegistryFriendlyByteBuf;
|
||||
import net.minecraft.network.codec.ByteBufCodecs;
|
||||
import net.minecraft.network.codec.StreamCodec;
|
||||
import net.minecraft.resources.RegistryFileCodec;
|
||||
import net.minecraft.resources.ResourceKey;
|
||||
import net.minecraft.resources.ResourceLocation;
|
||||
import org.jspecify.annotations.Nullable;
|
||||
|
||||
/**
|
||||
* A cosmetic overlay on a turtle.
|
||||
*
|
||||
* @param model The path to the overlay's model.
|
||||
* @param showElfOverlay Whether this overlay is compatible with the {@linkplain #ELF_MODEL Christmas elf model}.
|
||||
* @see ModRegistry.DataComponents#OVERLAY
|
||||
*/
|
||||
public record TurtleOverlay(ResourceLocation model, boolean showElfOverlay) {
|
||||
/**
|
||||
* The registry turtle overlays are stored in.
|
||||
*/
|
||||
public static final ResourceKey<Registry<TurtleOverlay>> REGISTRY = ResourceKey.createRegistryKey(ResourceLocation.fromNamespaceAndPath(ComputerCraftAPI.MOD_ID, "turtle_overlay"));
|
||||
|
||||
/**
|
||||
* The codec used to read/write turtle overlay definitions from datapacks.
|
||||
*/
|
||||
public static final Codec<TurtleOverlay> DIRECT_CODEC = RecordCodecBuilder.create(instance -> instance.group(
|
||||
ResourceLocation.CODEC.fieldOf("model").forGetter(TurtleOverlay::model),
|
||||
Codec.BOOL.optionalFieldOf("show_elf_overlay", false).forGetter(TurtleOverlay::showElfOverlay)
|
||||
).apply(instance, TurtleOverlay::new));
|
||||
|
||||
/**
|
||||
* The codec used for {@link TurtleOverlay} instances.
|
||||
*
|
||||
* @see ModRegistry.DataComponents#OVERLAY
|
||||
*/
|
||||
public static final Codec<Holder<TurtleOverlay>> CODEC = RegistryFileCodec.create(REGISTRY, DIRECT_CODEC);
|
||||
|
||||
/**
|
||||
* The stream codec used for {@link TurtleOverlay} instances.
|
||||
*/
|
||||
public static final StreamCodec<RegistryFriendlyByteBuf, Holder<TurtleOverlay>> STREAM_CODEC = ByteBufCodecs.holder(REGISTRY, StreamCodec.composite(
|
||||
ResourceLocation.STREAM_CODEC, TurtleOverlay::model,
|
||||
ByteBufCodecs.BOOL, TurtleOverlay::showElfOverlay,
|
||||
TurtleOverlay::new
|
||||
));
|
||||
|
||||
/**
|
||||
* An additional overlay that is rendered on all turtles at {@linkplain Holiday#CHRISTMAS Christmas}.
|
||||
*
|
||||
* @see #showElfOverlay()
|
||||
* @see #showElfOverlay(TurtleOverlay, boolean)
|
||||
*/
|
||||
public static final ResourceLocation ELF_MODEL = ResourceLocation.fromNamespaceAndPath(ComputerCraftAPI.MOD_ID, "block/turtle_elf_overlay");
|
||||
|
||||
/**
|
||||
* Determine whether we should show the {@linkplain #ELF_MODEL elf overlay}.
|
||||
*
|
||||
* @param overlay The current {@link TurtleOverlay}.
|
||||
* @param christmas Whether it is Christmas.
|
||||
* @return Whether we should show the elf overlay.
|
||||
*/
|
||||
public static boolean showElfOverlay(@Nullable TurtleOverlay overlay, boolean christmas) {
|
||||
return christmas && (overlay == null || overlay.showElfOverlay());
|
||||
}
|
||||
}
|
@ -21,7 +21,6 @@ import dan200.computercraft.shared.computer.core.ServerComputer;
|
||||
import dan200.computercraft.shared.config.Config;
|
||||
import dan200.computercraft.shared.container.BasicContainer;
|
||||
import dan200.computercraft.shared.platform.PlatformHelper;
|
||||
import dan200.computercraft.shared.turtle.TurtleOverlay;
|
||||
import dan200.computercraft.shared.turtle.core.TurtleBrain;
|
||||
import dan200.computercraft.shared.turtle.inventory.TurtleMenu;
|
||||
import net.minecraft.core.BlockPos;
|
||||
@ -33,6 +32,7 @@ import net.minecraft.core.component.DataComponentMap;
|
||||
import net.minecraft.core.component.DataComponents;
|
||||
import net.minecraft.nbt.CompoundTag;
|
||||
import net.minecraft.network.protocol.game.ClientboundBlockEntityDataPacket;
|
||||
import net.minecraft.resources.ResourceLocation;
|
||||
import net.minecraft.server.level.ServerLevel;
|
||||
import net.minecraft.world.Container;
|
||||
import net.minecraft.world.ContainerHelper;
|
||||
@ -233,9 +233,8 @@ public class TurtleBlockEntity extends AbstractComputerBlockEntity implements Ba
|
||||
return brain.getColour();
|
||||
}
|
||||
|
||||
public @Nullable TurtleOverlay getOverlay() {
|
||||
var overlay = brain.getOverlay();
|
||||
return overlay == null ? null : overlay.value();
|
||||
public @Nullable ResourceLocation getOverlay() {
|
||||
return brain.getOverlay();
|
||||
}
|
||||
|
||||
public ITurtleAccess getAccess() {
|
||||
|
@ -20,7 +20,6 @@ import dan200.computercraft.shared.computer.core.ComputerFamily;
|
||||
import dan200.computercraft.shared.computer.core.ServerComputer;
|
||||
import dan200.computercraft.shared.config.Config;
|
||||
import dan200.computercraft.shared.container.InventoryDelegate;
|
||||
import dan200.computercraft.shared.turtle.TurtleOverlay;
|
||||
import dan200.computercraft.shared.turtle.blocks.TurtleBlockEntity;
|
||||
import dan200.computercraft.shared.util.BlockEntityHelpers;
|
||||
import dan200.computercraft.shared.util.Holiday;
|
||||
@ -32,6 +31,7 @@ import net.minecraft.core.HolderLookup;
|
||||
import net.minecraft.core.component.DataComponentPatch;
|
||||
import net.minecraft.core.particles.ParticleTypes;
|
||||
import net.minecraft.nbt.CompoundTag;
|
||||
import net.minecraft.resources.ResourceLocation;
|
||||
import net.minecraft.server.level.ServerLevel;
|
||||
import net.minecraft.tags.FluidTags;
|
||||
import net.minecraft.world.Container;
|
||||
@ -74,7 +74,7 @@ public class TurtleBrain implements TurtleAccessInternal {
|
||||
private int selectedSlot = 0;
|
||||
private int fuelLevel = 0;
|
||||
private int colourHex = -1;
|
||||
private @Nullable Holder<TurtleOverlay> overlay = null;
|
||||
private @Nullable ResourceLocation overlay = null;
|
||||
|
||||
private TurtleAnimation animation = TurtleAnimation.NONE;
|
||||
private int animationProgress = 0;
|
||||
@ -134,7 +134,7 @@ public class TurtleBrain implements TurtleAccessInternal {
|
||||
// Read fields
|
||||
colourHex = nbt.getIntOr(NBT_COLOUR, -1);
|
||||
fuelLevel = nbt.getIntOr(NBT_FUEL, 0);
|
||||
overlay = nbt.contains(NBT_OVERLAY) ? NBTUtil.decodeFrom(TurtleOverlay.CODEC, registries, nbt, NBT_OVERLAY) : null;
|
||||
overlay = nbt.contains(NBT_OVERLAY) ? NBTUtil.decodeFrom(ResourceLocation.CODEC, registries, nbt, NBT_OVERLAY) : null;
|
||||
|
||||
// Read upgrades
|
||||
setUpgradeDirect(TurtleSide.LEFT, NBTUtil.decodeFrom(TurtleUpgrades.instance().upgradeDataCodec(), registries, nbt, NBT_LEFT_UPGRADE));
|
||||
@ -144,7 +144,7 @@ public class TurtleBrain implements TurtleAccessInternal {
|
||||
private void writeCommon(CompoundTag nbt, HolderLookup.Provider registries) {
|
||||
nbt.putInt(NBT_FUEL, fuelLevel);
|
||||
if (colourHex != -1) nbt.putInt(NBT_COLOUR, colourHex);
|
||||
NBTUtil.encodeTo(TurtleOverlay.CODEC, registries, nbt, NBT_OVERLAY, overlay);
|
||||
NBTUtil.encodeTo(ResourceLocation.CODEC, registries, nbt, NBT_OVERLAY, overlay);
|
||||
|
||||
// Write upgrades
|
||||
NBTUtil.encodeTo(TurtleUpgrades.instance().upgradeDataCodec(), registries, nbt, NBT_LEFT_UPGRADE, getUpgradeWithData(TurtleSide.LEFT));
|
||||
@ -418,11 +418,11 @@ public class TurtleBrain implements TurtleAccessInternal {
|
||||
BlockEntityHelpers.updateBlock(owner);
|
||||
}
|
||||
|
||||
public @Nullable Holder<TurtleOverlay> getOverlay() {
|
||||
public @Nullable ResourceLocation getOverlay() {
|
||||
return overlay;
|
||||
}
|
||||
|
||||
public void setOverlay(@Nullable Holder<TurtleOverlay> overlay) {
|
||||
public void setOverlay(@Nullable ResourceLocation overlay) {
|
||||
if (!Objects.equals(this.overlay, overlay)) {
|
||||
this.overlay = overlay;
|
||||
BlockEntityHelpers.updateBlock(owner);
|
||||
|
@ -11,12 +11,12 @@ import dan200.computercraft.api.upgrades.UpgradeData;
|
||||
import dan200.computercraft.impl.TurtleUpgrades;
|
||||
import dan200.computercraft.impl.UpgradeManager;
|
||||
import dan200.computercraft.shared.ModRegistry;
|
||||
import dan200.computercraft.shared.turtle.TurtleOverlay;
|
||||
import dan200.computercraft.shared.turtle.blocks.TurtleBlock;
|
||||
import net.minecraft.core.HolderLookup;
|
||||
import net.minecraft.core.cauldron.CauldronInteraction;
|
||||
import net.minecraft.core.component.DataComponents;
|
||||
import net.minecraft.network.chat.Component;
|
||||
import net.minecraft.resources.ResourceLocation;
|
||||
import net.minecraft.world.InteractionResult;
|
||||
import net.minecraft.world.item.BlockItem;
|
||||
import net.minecraft.world.item.ItemStack;
|
||||
@ -51,9 +51,8 @@ public class TurtleItem extends BlockItem {
|
||||
});
|
||||
}
|
||||
|
||||
public static @Nullable TurtleOverlay getOverlay(ItemStack stack) {
|
||||
var overlay = stack.get(ModRegistry.DataComponents.OVERLAY.get());
|
||||
return overlay == null ? null : overlay.value();
|
||||
public static @Nullable ResourceLocation getOverlay(ItemStack stack) {
|
||||
return stack.get(ModRegistry.DataComponents.OVERLAY.get());
|
||||
}
|
||||
|
||||
public static final CauldronInteraction CAULDRON_INTERACTION = (blockState, level, pos, player, hand, stack) -> {
|
||||
|
@ -1,56 +0,0 @@
|
||||
// SPDX-FileCopyrightText: 2023 The CC: Tweaked Developers
|
||||
//
|
||||
// SPDX-License-Identifier: MPL-2.0
|
||||
|
||||
package dan200.computercraft.shared.util;
|
||||
|
||||
import org.jspecify.annotations.Nullable;
|
||||
|
||||
import java.util.AbstractList;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* A list which prepends a single value to another list.
|
||||
*
|
||||
* @param <T> The type of item in the list.
|
||||
*/
|
||||
public final class ConsList<T> extends AbstractList<T> {
|
||||
private final T head;
|
||||
private final List<T> tail;
|
||||
|
||||
public ConsList(T head, List<T> tail) {
|
||||
this.head = head;
|
||||
this.tail = tail;
|
||||
}
|
||||
|
||||
@Override
|
||||
public T get(int index) {
|
||||
return index == 0 ? head : tail.get(index - 1);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int size() {
|
||||
return 1 + tail.size();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Iterator<T> iterator() {
|
||||
return new Iterator<>() {
|
||||
private @Nullable Iterator<T> tailIterator;
|
||||
|
||||
@Override
|
||||
public boolean hasNext() {
|
||||
return tailIterator == null || tailIterator.hasNext();
|
||||
}
|
||||
|
||||
@Override
|
||||
public T next() {
|
||||
if (tailIterator != null) return tailIterator.next();
|
||||
|
||||
tailIterator = tail.iterator();
|
||||
return head;
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
@ -0,0 +1,70 @@
|
||||
// SPDX-FileCopyrightText: 2025 The CC: Tweaked Developers
|
||||
//
|
||||
// SPDX-License-Identifier: MPL-2.0
|
||||
|
||||
package dan200.computercraft.shared.util;
|
||||
|
||||
import com.google.gson.JsonElement;
|
||||
import com.google.gson.JsonParser;
|
||||
import com.mojang.datafixers.util.Pair;
|
||||
import com.mojang.serialization.Codec;
|
||||
import com.mojang.serialization.DynamicOps;
|
||||
import net.minecraft.Util;
|
||||
import net.minecraft.resources.FileToIdConverter;
|
||||
import net.minecraft.resources.ResourceLocation;
|
||||
import net.minecraft.server.packs.resources.ResourceManager;
|
||||
import net.minecraft.server.packs.resources.SimpleJsonResourceReloadListener;
|
||||
import org.jspecify.annotations.Nullable;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.Executor;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
public class ResourceUtils {
|
||||
private static final Logger LOG = LoggerFactory.getLogger(ResourceUtils.class);
|
||||
|
||||
/**
|
||||
* A version of {@link SimpleJsonResourceReloadListener#scanDirectory(ResourceManager, FileToIdConverter, DynamicOps, Codec, Map)},
|
||||
* that runs in parallel over multiple executors.
|
||||
* <p>
|
||||
* This shares some similarity with how {@link net.minecraft.client.resources.model.ModelManager#loadBlockModels(ResourceManager, Executor)}
|
||||
* loads resources.
|
||||
*
|
||||
* @param resourceManager The current resource manager.
|
||||
* @param executor The executor to schedule work on.
|
||||
* @param kind The name of the thing we're loading, for use in error messages.
|
||||
* @param lister The file-to-id-converter used to locate files.
|
||||
* @param ops The dynamic ops used when decoding.
|
||||
* @param codec The codec used to decode each file.
|
||||
* @param <T> The type of the entry to load
|
||||
* @return The loaded resources.
|
||||
*/
|
||||
public static <T> CompletableFuture<Map<ResourceLocation, T>> load(
|
||||
ResourceManager resourceManager, Executor executor, String kind, FileToIdConverter lister, DynamicOps<JsonElement> ops, Codec<T> codec
|
||||
) {
|
||||
return CompletableFuture.supplyAsync(() -> lister.listMatchingResources(resourceManager), executor).thenCompose(resources -> {
|
||||
List<CompletableFuture<@Nullable Pair<ResourceLocation, T>>> futures = new ArrayList<>(resources.size());
|
||||
resources.forEach((path, resource) -> futures.add(CompletableFuture.supplyAsync(() -> {
|
||||
var id = lister.fileToId(path);
|
||||
try (var reader = resource.openAsReader()) {
|
||||
var result = codec.parse(ops, JsonParser.parseReader(reader))
|
||||
.ifError(e -> LOG.error("Couldn't parse {} '{}' from pack '{}': {}", kind, id, resource.sourcePackId(), e.message()))
|
||||
.result().orElse(null);
|
||||
return result == null ? null : new Pair<>(id, result);
|
||||
} catch (Exception exception) {
|
||||
LOG.error("Failed to open {} {} from pack '{}'", kind, resource, resource.sourcePackId(), exception);
|
||||
return null;
|
||||
}
|
||||
}, executor)));
|
||||
|
||||
return Util.sequence(futures).thenApply(result -> result
|
||||
.stream().filter(Objects::nonNull).collect(Collectors.toUnmodifiableMap(Pair::getFirst, Pair::getSecond)));
|
||||
});
|
||||
}
|
||||
}
|
@ -1,37 +0,0 @@
|
||||
// SPDX-FileCopyrightText: 2023 The CC: Tweaked Developers
|
||||
//
|
||||
// SPDX-License-Identifier: MPL-2.0
|
||||
|
||||
package dan200.computercraft.shared.util;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertArrayEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
|
||||
/**
|
||||
* A couple of trivial tests for {@link ConsList}, mostly as a quick safety check.
|
||||
*/
|
||||
public class ConsListTest {
|
||||
@Test
|
||||
public void testGet() {
|
||||
var list = new ConsList<>(1, List.of(2, 3, 4));
|
||||
assertEquals(1, list.get(0));
|
||||
assertEquals(2, list.get(1));
|
||||
assertEquals(4, list.get(3));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSize() {
|
||||
var list = new ConsList<>(1, List.of(2, 3, 4));
|
||||
assertEquals(4, list.size());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testIterator() {
|
||||
var list = new ConsList<>(1, List.of(2, 3, 4));
|
||||
assertArrayEquals(new Integer[]{ 1, 2, 3, 4 }, list.toArray(Integer[]::new));
|
||||
}
|
||||
}
|
@ -14,7 +14,6 @@ import dan200.computercraft.api.turtle.TurtleSide
|
||||
import dan200.computercraft.api.upgrades.UpgradeData
|
||||
import dan200.computercraft.core.apis.PeripheralAPI
|
||||
import dan200.computercraft.gametest.api.*
|
||||
import dan200.computercraft.gametest.api.GameTest
|
||||
import dan200.computercraft.gametest.core.TestHooks
|
||||
import dan200.computercraft.mixin.gametest.GameTestHelperAccessor
|
||||
import dan200.computercraft.mixin.gametest.GameTestInfoAccessor
|
||||
@ -24,7 +23,6 @@ import dan200.computercraft.shared.peripheral.modem.wired.CableBlock
|
||||
import dan200.computercraft.shared.peripheral.modem.wired.CableModemVariant
|
||||
import dan200.computercraft.shared.peripheral.monitor.MonitorBlock
|
||||
import dan200.computercraft.shared.peripheral.monitor.MonitorEdgeState
|
||||
import dan200.computercraft.shared.turtle.TurtleOverlay
|
||||
import dan200.computercraft.shared.turtle.apis.TurtleAPI
|
||||
import dan200.computercraft.shared.turtle.blocks.TurtleBlockEntity
|
||||
import dan200.computercraft.shared.turtle.core.TurtleCraftCommand
|
||||
@ -772,8 +770,7 @@ class Turtle_Test {
|
||||
@GameTest
|
||||
fun Data_fixers(helper: GameTestHelper) = helper.sequence {
|
||||
thenExecute {
|
||||
val overlay = helper.level.registryAccess().lookupOrThrow(TurtleOverlay.REGISTRY)
|
||||
.getValue(ResourceLocation.fromNamespaceAndPath(ComputerCraftAPI.MOD_ID, "trans_flag"))!!
|
||||
val overlay = ResourceLocation.fromNamespaceAndPath(ComputerCraftAPI.MOD_ID, "trans_flag")
|
||||
val upgrade = helper.level.registryAccess().lookupOrThrow(ITurtleUpgrade.REGISTRY)
|
||||
.getValue(ResourceLocation.withDefaultNamespace("diamond_pickaxe"))!!
|
||||
|
||||
|
@ -9,11 +9,8 @@ import com.mojang.brigadier.builder.LiteralArgumentBuilder;
|
||||
import com.mojang.brigadier.builder.RequiredArgumentBuilder;
|
||||
import dan200.computercraft.api.ComputerCraftAPI;
|
||||
import dan200.computercraft.api.client.FabricComputerCraftAPIClient;
|
||||
import dan200.computercraft.api.client.StandaloneModel;
|
||||
import dan200.computercraft.api.client.turtle.TurtleUpgradeModel;
|
||||
import dan200.computercraft.api.turtle.ITurtleUpgrade;
|
||||
import dan200.computercraft.client.model.ExtraModels;
|
||||
import dan200.computercraft.client.platform.FabricModelKey;
|
||||
import dan200.computercraft.client.platform.ModelKey;
|
||||
import dan200.computercraft.core.util.Nullability;
|
||||
import dan200.computercraft.impl.Services;
|
||||
import dan200.computercraft.shared.ComputerCraft;
|
||||
@ -27,7 +24,6 @@ import net.fabricmc.fabric.api.client.command.v2.ClientCommandRegistrationCallba
|
||||
import net.fabricmc.fabric.api.client.command.v2.FabricClientCommandSource;
|
||||
import net.fabricmc.fabric.api.client.event.lifecycle.v1.ClientTickEvents;
|
||||
import net.fabricmc.fabric.api.client.model.loading.v1.PreparableModelLoadingPlugin;
|
||||
import net.fabricmc.fabric.api.client.model.loading.v1.SimpleUnbakedExtraModel;
|
||||
import net.fabricmc.fabric.api.client.model.loading.v1.UnbakedExtraModel;
|
||||
import net.fabricmc.fabric.api.client.networking.v1.ClientPlayNetworking;
|
||||
import net.fabricmc.fabric.api.client.rendering.v1.WorldRenderEvents;
|
||||
@ -41,11 +37,13 @@ import net.minecraft.client.renderer.item.ItemModels;
|
||||
import net.minecraft.client.renderer.item.properties.conditional.ConditionalItemModelProperties;
|
||||
import net.minecraft.client.renderer.item.properties.select.SelectItemModelProperties;
|
||||
import net.minecraft.client.resources.model.ModelBaker;
|
||||
import net.minecraft.client.resources.model.ResolvableModel;
|
||||
import net.minecraft.world.phys.BlockHitResult;
|
||||
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.function.BiConsumer;
|
||||
import java.util.function.BiFunction;
|
||||
|
||||
import static dan200.computercraft.core.util.Nullability.assertNonNull;
|
||||
|
||||
@ -67,12 +65,13 @@ public class ComputerCraftClient {
|
||||
ClientRegistry.registerConditionalItemProperties(ConditionalItemModelProperties.ID_MAPPER::put);
|
||||
|
||||
PreparableModelLoadingPlugin.register(
|
||||
(resources, executor) -> CompletableFuture.supplyAsync(() -> ExtraModels.loadAll(resources), executor),
|
||||
(state, context) -> ClientRegistry.registerExtraModels(
|
||||
(key, model) -> context.addModel(FabricModelKey.key(key), new SimpleUnbakedExtraModel<>(model, StandaloneModel::of)),
|
||||
(key, model) -> context.addModel(FabricModelKey.erased(key), new TurtleModelWrapper<>(model)),
|
||||
state
|
||||
)
|
||||
ClientRegistry::gatherExtraModels,
|
||||
(state, context) -> ClientRegistry.registerExtraModels(new ClientRegistry.RegisterExtraModels() {
|
||||
@Override
|
||||
public <U, T> void register(ModelKey<T> key, U unbaked, BiConsumer<U, ResolvableModel.Resolver> resolve, BiFunction<U, ModelBaker, T> bake) {
|
||||
context.addModel(FabricModelKey.key(key), new ModelWrapper<>(unbaked, resolve, bake));
|
||||
}
|
||||
}, state)
|
||||
);
|
||||
|
||||
BlockRenderLayerMap.INSTANCE.putBlock(ModRegistry.Blocks.COMPUTER_NORMAL.get(), RenderType.cutout());
|
||||
@ -108,17 +107,17 @@ public class ComputerCraftClient {
|
||||
((FabricConfigFile) ConfigSpec.clientSpec).load(FabricLoader.getInstance().getConfigDir().resolve(ComputerCraftAPI.MOD_ID + "-client.toml"));
|
||||
}
|
||||
|
||||
private record TurtleModelWrapper<T extends ITurtleUpgrade>(
|
||||
TurtleUpgradeModel.Unbaked<T> model
|
||||
) implements UnbakedExtraModel<TurtleUpgradeModel<T>> {
|
||||
private record ModelWrapper<U, T>(
|
||||
U model, BiConsumer<U, Resolver> resolve, BiFunction<U, ModelBaker, T> bake
|
||||
) implements UnbakedExtraModel<T> {
|
||||
@Override
|
||||
public TurtleUpgradeModel<T> bake(ModelBaker baker) {
|
||||
return model().bake(baker);
|
||||
public T bake(ModelBaker baker) {
|
||||
return bake().apply(model(), baker);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void resolveDependencies(Resolver resolver) {
|
||||
model().resolveDependencies(resolver);
|
||||
resolve().accept(model(), resolver);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -24,11 +24,6 @@ public final class FabricModelKey<T> implements ModelKey<T> {
|
||||
return ((FabricModelKey<T>) key).key;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public static <T> ExtraModelKey<T> erased(ModelKey<?> key) {
|
||||
return ((FabricModelKey<T>) key).key;
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nullable T get(ModelManager manager) {
|
||||
return manager.getModel(key);
|
||||
|
@ -23,7 +23,6 @@ import dan200.computercraft.shared.peripheral.generic.methods.InventoryMethods;
|
||||
import dan200.computercraft.shared.peripheral.modem.wired.CableBlock;
|
||||
import dan200.computercraft.shared.platform.FabricConfigFile;
|
||||
import dan200.computercraft.shared.recipe.function.RecipeFunction;
|
||||
import dan200.computercraft.shared.turtle.TurtleOverlay;
|
||||
import net.fabricmc.fabric.api.command.v2.CommandRegistrationCallback;
|
||||
import net.fabricmc.fabric.api.event.lifecycle.v1.ServerChunkEvents;
|
||||
import net.fabricmc.fabric.api.event.lifecycle.v1.ServerLifecycleEvents;
|
||||
@ -85,7 +84,6 @@ public class ComputerCraft {
|
||||
|
||||
DynamicRegistries.registerSynced(ITurtleUpgrade.REGISTRY, TurtleUpgrades.instance().upgradeCodec());
|
||||
DynamicRegistries.registerSynced(IPocketUpgrade.REGISTRY, PocketUpgrades.instance().upgradeCodec());
|
||||
DynamicRegistries.registerSynced(TurtleOverlay.REGISTRY, TurtleOverlay.DIRECT_CODEC);
|
||||
|
||||
ModRegistry.register();
|
||||
ModRegistry.registerMainThread();
|
||||
|
@ -6,12 +6,9 @@ package dan200.computercraft.client;
|
||||
|
||||
import com.google.common.reflect.TypeToken;
|
||||
import dan200.computercraft.api.ComputerCraftAPI;
|
||||
import dan200.computercraft.api.client.StandaloneModel;
|
||||
import dan200.computercraft.api.client.turtle.RegisterTurtleModelEvent;
|
||||
import dan200.computercraft.api.client.turtle.TurtleUpgradeModel;
|
||||
import dan200.computercraft.api.turtle.ITurtleUpgrade;
|
||||
import dan200.computercraft.client.model.ExtraModels;
|
||||
import dan200.computercraft.client.platform.ForgeModelKey;
|
||||
import dan200.computercraft.client.platform.ModelKey;
|
||||
import dan200.computercraft.client.render.ExtendedItemFrameRenderState;
|
||||
import dan200.computercraft.client.turtle.TurtleUpgradeModels;
|
||||
import net.minecraft.client.Minecraft;
|
||||
@ -29,6 +26,11 @@ import net.neoforged.neoforge.client.event.*;
|
||||
import net.neoforged.neoforge.client.model.standalone.UnbakedStandaloneModel;
|
||||
import net.neoforged.neoforge.client.renderstate.RegisterRenderStateModifiersEvent;
|
||||
|
||||
import java.util.ArrayDeque;
|
||||
import java.util.Queue;
|
||||
import java.util.function.BiConsumer;
|
||||
import java.util.function.BiFunction;
|
||||
|
||||
|
||||
/**
|
||||
* Registers textures and models for items.
|
||||
@ -44,12 +46,18 @@ public final class ForgeClientRegistry {
|
||||
public static void registerModels(ModelEvent.RegisterStandalone event) {
|
||||
TurtleUpgradeModels.fetch(() -> ModLoader.postEvent(new RegisterTurtleModelEvent(TurtleUpgradeModels::register)));
|
||||
|
||||
var extraModels = ExtraModels.loadAll(Minecraft.getInstance().getResourceManager());
|
||||
ClientRegistry.registerExtraModels(
|
||||
(key, model) -> event.register(ForgeModelKey.key(key), StandaloneModel::of),
|
||||
(key, model) -> event.register(ForgeModelKey.erased(key), new TurtleModelWrapper<>(model)),
|
||||
extraModels
|
||||
);
|
||||
// Load resources
|
||||
Queue<Runnable> tasks = new ArrayDeque<>();
|
||||
var state = ClientRegistry.gatherExtraModels(Minecraft.getInstance().getResourceManager(), tasks::add);
|
||||
Runnable task;
|
||||
while ((task = tasks.poll()) != null) task.run();
|
||||
|
||||
ClientRegistry.registerExtraModels(new ClientRegistry.RegisterExtraModels() {
|
||||
@Override
|
||||
public <U, T> void register(ModelKey<T> key, U unbaked, BiConsumer<U, ResolvableModel.Resolver> resolve, BiFunction<U, ModelBaker, T> bake) {
|
||||
event.register(ForgeModelKey.key(key), new ModelWrapper<>(unbaked, resolve, bake));
|
||||
}
|
||||
}, state.resultNow());
|
||||
}
|
||||
|
||||
@SubscribeEvent
|
||||
@ -102,17 +110,17 @@ public final class ForgeClientRegistry {
|
||||
ClientRegistry.register();
|
||||
}
|
||||
|
||||
private record TurtleModelWrapper<T extends ITurtleUpgrade>(
|
||||
TurtleUpgradeModel.Unbaked<T> model
|
||||
) implements UnbakedStandaloneModel<TurtleUpgradeModel<T>> {
|
||||
private record ModelWrapper<U, T>(
|
||||
U model, BiConsumer<U, ResolvableModel.Resolver> resolve, BiFunction<U, ModelBaker, T> bake
|
||||
) implements UnbakedStandaloneModel<T> {
|
||||
@Override
|
||||
public TurtleUpgradeModel<T> bake(ModelBaker baker) {
|
||||
return model().bake(baker);
|
||||
public T bake(ModelBaker baker) {
|
||||
return bake().apply(model(), baker);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void resolveDependencies(ResolvableModel.Resolver resolver) {
|
||||
model().resolveDependencies(resolver);
|
||||
resolve().accept(model(), resolver);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -24,11 +24,6 @@ public final class ForgeModelKey<T> implements ModelKey<T> {
|
||||
return ((ForgeModelKey<T>) key).key;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public static <T> StandaloneModelKey<T> erased(ModelKey<?> key) {
|
||||
return ((ForgeModelKey<T>) key).key;
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nullable T get(ModelManager manager) {
|
||||
return manager.getStandaloneModel(key);
|
||||
|
@ -30,7 +30,6 @@ import dan200.computercraft.shared.peripheral.generic.methods.FluidMethods;
|
||||
import dan200.computercraft.shared.peripheral.generic.methods.InventoryMethods;
|
||||
import dan200.computercraft.shared.platform.ForgeConfigFile;
|
||||
import dan200.computercraft.shared.recipe.function.RecipeFunction;
|
||||
import dan200.computercraft.shared.turtle.TurtleOverlay;
|
||||
import net.minecraft.core.registries.BuiltInRegistries;
|
||||
import net.minecraft.network.RegistryFriendlyByteBuf;
|
||||
import net.minecraft.network.protocol.common.custom.CustomPacketPayload;
|
||||
@ -104,7 +103,6 @@ public final class ComputerCraft {
|
||||
public static void registerDynamicRegistries(DataPackRegistryEvent.NewRegistry event) {
|
||||
event.dataPackRegistry(ITurtleUpgrade.REGISTRY, TurtleUpgrades.instance().upgradeCodec(), TurtleUpgrades.instance().upgradeCodec());
|
||||
event.dataPackRegistry(IPocketUpgrade.REGISTRY, PocketUpgrades.instance().upgradeCodec(), PocketUpgrades.instance().upgradeCodec());
|
||||
event.dataPackRegistry(TurtleOverlay.REGISTRY, TurtleOverlay.DIRECT_CODEC, TurtleOverlay.DIRECT_CODEC);
|
||||
}
|
||||
|
||||
@SubscribeEvent
|
||||
|
Loading…
x
Reference in New Issue
Block a user