mirror of
https://github.com/SquidDev-CC/CC-Tweaked
synced 2025-10-17 15:07:38 +00:00
Compare commits
1 Commits
v1.21.1-1.
...
v1.21.4-1.
Author | SHA1 | Date | |
---|---|---|---|
![]() |
9277aa33e9 |
@@ -4,10 +4,7 @@
|
||||
|
||||
/** Default configuration for Fabric projects. */
|
||||
|
||||
import cc.tweaked.gradle.CCTweakedExtension
|
||||
import cc.tweaked.gradle.CCTweakedPlugin
|
||||
import cc.tweaked.gradle.IdeaRunConfigurations
|
||||
import cc.tweaked.gradle.MinecraftConfigurations
|
||||
import cc.tweaked.gradle.*
|
||||
|
||||
plugins {
|
||||
`java-library`
|
||||
@@ -67,3 +64,9 @@ dependencies {
|
||||
tasks.ideaSyncTask {
|
||||
doLast { IdeaRunConfigurations(project).patch() }
|
||||
}
|
||||
|
||||
tasks.named("checkDependencyConsistency", DependencyCheck::class.java) {
|
||||
val libs = project.extensions.getByType<VersionCatalogsExtension>().named("libs")
|
||||
// Minecraft depends on asm, but Fabric forces it to a more recent version
|
||||
override(libs.findLibrary("asm").get(), "9.7.1")
|
||||
}
|
||||
|
@@ -129,7 +129,7 @@ SPDX-License-Identifier: MPL-2.0
|
||||
<module name="LocalFinalVariableName" />
|
||||
<module name="LocalVariableName" />
|
||||
<module name="MemberName">
|
||||
<property name="format" value="^\$?[a-z][a-zA-Z0-9]*$" />
|
||||
<property name="format" value="^(computercraft\$|\$)?[a-z][a-zA-Z0-9]*$" />
|
||||
</module>
|
||||
<module name="MethodName">
|
||||
<property name="format" value="^(computercraft\$)?[a-z][a-zA-Z0-9]*$" />
|
||||
|
@@ -15,4 +15,4 @@ isUnstable=true
|
||||
modVersion=1.115.1
|
||||
|
||||
# Minecraft properties: We want to configure this here so we can read it in settings.gradle
|
||||
mcVersion=1.21.1
|
||||
mcVersion=1.21.4
|
||||
|
@@ -7,23 +7,23 @@
|
||||
# Minecraft
|
||||
# MC version is specified in gradle.properties, as we need that in settings.gradle.
|
||||
# Remember to update corresponding versions in fabric.mod.json/neoforge.mods.toml
|
||||
fabric-api = "0.102.1+1.21.1"
|
||||
fabric-loader = "0.15.11"
|
||||
neoForge = "21.1.9"
|
||||
fabric-api = "0.118.0+1.21.4"
|
||||
fabric-loader = "0.16.10"
|
||||
neoForge = "21.4.101-beta"
|
||||
neoForgeSpi = "8.0.1"
|
||||
mixin = "0.8.5"
|
||||
parchment = "2024.07.28"
|
||||
parchmentMc = "1.21"
|
||||
yarn = "1.21.1+build.1"
|
||||
parchment = "2024.12.07"
|
||||
parchmentMc = "1.21.4"
|
||||
yarn = "1.21.4+build.1"
|
||||
|
||||
# Core dependencies (these versions are tied to the version Minecraft uses)
|
||||
fastutil = "8.5.12"
|
||||
guava = "32.1.2-jre"
|
||||
netty = "4.1.97.Final"
|
||||
slf4j = "2.0.9"
|
||||
fastutil = "8.5.15"
|
||||
guava = "33.3.1-jre"
|
||||
netty = "4.1.115.Final"
|
||||
slf4j = "2.0.16"
|
||||
|
||||
# Core dependencies (independent of Minecraft)
|
||||
asm = "9.6"
|
||||
asm = "9.7.1"
|
||||
autoService = "1.1.1"
|
||||
checkerFramework = "3.42.0"
|
||||
cobalt = { strictly = "0.9.5" }
|
||||
@@ -37,15 +37,15 @@ nightConfig = "3.8.1"
|
||||
|
||||
# Minecraft mods
|
||||
emi = "1.1.7+1.21"
|
||||
fabricPermissions = "0.3.1"
|
||||
iris-fabric = "1.8.0-beta.3+1.21-fabric"
|
||||
iris-forge = "1.8.0-beta.3+1.21-neoforge"
|
||||
fabricPermissions = "0.3.3"
|
||||
iris-fabric = "1.8.8+1.21.4-fabric"
|
||||
iris-forge = "1.8.8+1.21.4-neoforge"
|
||||
jei = "19.8.2.99"
|
||||
modmenu = "11.0.0-rc.4"
|
||||
modmenu = "13.0.2"
|
||||
moreRed = "6.0.0.3"
|
||||
rei = "16.0.729"
|
||||
sodium-fabric = "mc1.21-0.6.0-beta.1-fabric"
|
||||
sodium-forge = "mc1.21-0.6.0-beta.1-neoforge"
|
||||
rei = "18.0.800"
|
||||
sodium-fabric = "mc1.21.4-0.6.10-fabric"
|
||||
sodium-forge = "mc1.21.4-0.6.10-neoforge"
|
||||
mixinExtra = "0.3.5"
|
||||
create-forge = "6.0.0-6"
|
||||
create-fabric = "0.5.1-f-build.1467+mc1.20.1"
|
||||
@@ -186,9 +186,9 @@ kotlin = ["kotlin-stdlib", "kotlin-coroutines"]
|
||||
# Minecraft
|
||||
externalMods-common = ["iris-forge", "jei-api", "nightConfig-core", "nightConfig-toml"]
|
||||
externalMods-forge-compile = ["moreRed", "iris-forge", "jei-api"]
|
||||
externalMods-forge-runtime = ["jei-forge"]
|
||||
externalMods-forge-runtime = []
|
||||
externalMods-fabric-compile = ["fabricPermissions", "iris-fabric", "jei-api", "rei-api", "rei-builtin"]
|
||||
externalMods-fabric-runtime = ["jei-fabric", "modmenu"]
|
||||
externalMods-fabric-runtime = []
|
||||
|
||||
# Testing
|
||||
test = ["junit-jupiter-api", "junit-jupiter-params", "hamcrest", "jqwik-api"]
|
||||
|
@@ -1,84 +0,0 @@
|
||||
// SPDX-FileCopyrightText: 2024 The CC: Tweaked Developers
|
||||
//
|
||||
// SPDX-License-Identifier: MPL-2.0
|
||||
|
||||
package dan200.computercraft.api.client;
|
||||
|
||||
import dan200.computercraft.api.client.turtle.TurtleUpgradeModeller;
|
||||
import dan200.computercraft.impl.client.ClientPlatformHelper;
|
||||
import net.minecraft.client.resources.model.BakedModel;
|
||||
import net.minecraft.client.resources.model.ModelManager;
|
||||
import net.minecraft.client.resources.model.ModelResourceLocation;
|
||||
import net.minecraft.resources.ResourceLocation;
|
||||
import org.jspecify.annotations.Nullable;
|
||||
|
||||
import java.util.stream.Stream;
|
||||
|
||||
/**
|
||||
* The location of a model to load. This may either be:
|
||||
*
|
||||
* <ul>
|
||||
* <li>A {@link ModelResourceLocation}, referencing an already baked model (such as {@code minecraft:dirt#inventory}).</li>
|
||||
* <li>
|
||||
* A {@link ResourceLocation}, referencing a path to a model resource (such as {@code minecraft:item/dirt}.
|
||||
* These models will be baked and stored in the {@link ModelManager} in a loader-specific way.
|
||||
* </li>
|
||||
* </ul>
|
||||
*/
|
||||
public final class ModelLocation {
|
||||
/**
|
||||
* The location of the model.
|
||||
* <p>
|
||||
* When {@link #resourceLocation} is null, this is the location of the model to load. When {@link #resourceLocation}
|
||||
* is non-null, this is the "standalone" variant of the model resource — this is used by NeoForge's implementation
|
||||
* of {@link ClientPlatformHelper#getModel(ModelManager, ModelResourceLocation, ResourceLocation)} to fetch the
|
||||
* model from the model manger. It is not used on Fabric.
|
||||
*/
|
||||
private final ModelResourceLocation modelLocation;
|
||||
private final @Nullable ResourceLocation resourceLocation;
|
||||
|
||||
private ModelLocation(ModelResourceLocation modelLocation, @Nullable ResourceLocation resourceLocation) {
|
||||
this.modelLocation = modelLocation;
|
||||
this.resourceLocation = resourceLocation;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a {@link ModelLocation} from model in the model manager.
|
||||
*
|
||||
* @param location The name of the model to load.
|
||||
* @return The new {@link ModelLocation} instance.
|
||||
*/
|
||||
public static ModelLocation ofModel(ModelResourceLocation location) {
|
||||
return new ModelLocation(location, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a {@link ModelLocation} from a resource.
|
||||
*
|
||||
* @param location The location of the model resource, such as {@code minecraft:item/dirt}.
|
||||
* @return The new {@link ModelLocation} instance.
|
||||
*/
|
||||
public static ModelLocation ofResource(ResourceLocation location) {
|
||||
return new ModelLocation(new ModelResourceLocation(location, "standalone"), location);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get this model from the provided model manager.
|
||||
*
|
||||
* @param manager The model manger.
|
||||
* @return This model, or the missing model if it could not be found.
|
||||
*/
|
||||
public BakedModel getModel(ModelManager manager) {
|
||||
return ClientPlatformHelper.get().getModel(manager, modelLocation, resourceLocation);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the models this model location depends on.
|
||||
*
|
||||
* @return A list of models that this model location depends on.
|
||||
* @see TurtleUpgradeModeller#getDependencies()
|
||||
*/
|
||||
public Stream<ResourceLocation> getDependencies() {
|
||||
return resourceLocation == null ? Stream.empty() : Stream.of(resourceLocation);
|
||||
}
|
||||
}
|
@@ -9,19 +9,21 @@ import com.mojang.math.Transformation;
|
||||
import dan200.computercraft.impl.client.ClientPlatformHelper;
|
||||
import net.minecraft.client.Minecraft;
|
||||
import net.minecraft.client.resources.model.BakedModel;
|
||||
import net.minecraft.client.resources.model.ModelResourceLocation;
|
||||
import net.minecraft.resources.ResourceLocation;
|
||||
import net.minecraft.world.item.ItemStack;
|
||||
|
||||
/**
|
||||
* A model to render, combined with a transformation matrix to apply.
|
||||
*
|
||||
* @param model The model.
|
||||
* @param matrix The transformation matrix.
|
||||
*/
|
||||
public record TransformedModel(BakedModel model, Transformation matrix) {
|
||||
public TransformedModel(BakedModel model) {
|
||||
this(model, Transformation.identity());
|
||||
public sealed interface TransformedModel permits TransformedModel.Baked, TransformedModel.Item {
|
||||
record Baked(BakedModel model) implements TransformedModel {
|
||||
}
|
||||
|
||||
record Item(ItemStack stack, Transformation transformation) implements TransformedModel {
|
||||
}
|
||||
|
||||
static TransformedModel of(BakedModel model) {
|
||||
return new TransformedModel.Baked(model);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -30,37 +32,12 @@ public record TransformedModel(BakedModel model, Transformation matrix) {
|
||||
* @param location The location of the model to load.
|
||||
* @return The new {@link TransformedModel} instance.
|
||||
*/
|
||||
public static TransformedModel of(ModelLocation location) {
|
||||
static TransformedModel of(ResourceLocation location) {
|
||||
var modelManager = Minecraft.getInstance().getModelManager();
|
||||
return new TransformedModel(location.getModel(modelManager));
|
||||
return of(ClientPlatformHelper.get().getModel(modelManager, location));
|
||||
}
|
||||
|
||||
/**
|
||||
* Look up a model in the model bakery and construct a {@link TransformedModel} with no transformation.
|
||||
*
|
||||
* @param location The location of the model to load.
|
||||
* @return The new {@link TransformedModel} instance.
|
||||
* @see ModelLocation#ofModel(ModelResourceLocation)
|
||||
*/
|
||||
public static TransformedModel of(ModelResourceLocation location) {
|
||||
var modelManager = Minecraft.getInstance().getModelManager();
|
||||
return new TransformedModel(modelManager.getModel(location));
|
||||
}
|
||||
|
||||
/**
|
||||
* Look up a model in the model bakery and construct a {@link TransformedModel} with no transformation.
|
||||
*
|
||||
* @param location The location of the model to load.
|
||||
* @return The new {@link TransformedModel} instance.
|
||||
* @see ModelLocation#ofResource(ResourceLocation)
|
||||
*/
|
||||
public static TransformedModel of(ResourceLocation location) {
|
||||
var modelManager = Minecraft.getInstance().getModelManager();
|
||||
return new TransformedModel(ClientPlatformHelper.get().getModel(modelManager, location));
|
||||
}
|
||||
|
||||
public static TransformedModel of(ItemStack item, Transformation transform) {
|
||||
var model = Minecraft.getInstance().getItemRenderer().getItemModelShaper().getItemModel(item);
|
||||
return new TransformedModel(model, transform);
|
||||
static TransformedModel of(ItemStack item, Transformation transform) {
|
||||
return new TransformedModel.Item(item, transform);
|
||||
}
|
||||
}
|
||||
|
@@ -4,7 +4,6 @@
|
||||
|
||||
package dan200.computercraft.api.client.turtle;
|
||||
|
||||
import dan200.computercraft.api.client.ModelLocation;
|
||||
import dan200.computercraft.api.client.TransformedModel;
|
||||
import dan200.computercraft.api.turtle.ITurtleAccess;
|
||||
import dan200.computercraft.api.turtle.ITurtleUpgrade;
|
||||
@@ -55,7 +54,7 @@ public interface TurtleUpgradeModeller<T extends ITurtleUpgrade> {
|
||||
* by other means.
|
||||
*
|
||||
* @return A list of models that this modeller depends on.
|
||||
* @see UnbakedModel#getDependencies()
|
||||
* @see UnbakedModel#resolveDependencies(UnbakedModel.Resolver)
|
||||
*/
|
||||
default Stream<ResourceLocation> getDependencies() {
|
||||
return Stream.of();
|
||||
@@ -85,18 +84,6 @@ public interface TurtleUpgradeModeller<T extends ITurtleUpgrade> {
|
||||
* @return The constructed modeller.
|
||||
*/
|
||||
static <T extends ITurtleUpgrade> TurtleUpgradeModeller<T> sided(ResourceLocation left, ResourceLocation right) {
|
||||
return sided(ModelLocation.ofResource(left), ModelLocation.ofResource(right));
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct a {@link TurtleUpgradeModeller} which has a single model for the left and right side.
|
||||
*
|
||||
* @param left The model to use on the left.
|
||||
* @param right The model to use on the right.
|
||||
* @param <T> The type of the turtle upgrade.
|
||||
* @return The constructed modeller.
|
||||
*/
|
||||
static <T extends ITurtleUpgrade> TurtleUpgradeModeller<T> sided(ModelLocation left, ModelLocation right) {
|
||||
return new TurtleUpgradeModeller<>() {
|
||||
@Override
|
||||
public TransformedModel getModel(T upgrade, @Nullable ITurtleAccess turtle, TurtleSide side, DataComponentPatch data) {
|
||||
@@ -105,7 +92,7 @@ public interface TurtleUpgradeModeller<T extends ITurtleUpgrade> {
|
||||
|
||||
@Override
|
||||
public Stream<ResourceLocation> getDependencies() {
|
||||
return Stream.of(left, right).flatMap(ModelLocation::getDependencies);
|
||||
return Stream.of(left, right);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
@@ -4,31 +4,27 @@
|
||||
|
||||
package dan200.computercraft.api.client.turtle;
|
||||
|
||||
import com.mojang.math.Axis;
|
||||
import com.mojang.math.Transformation;
|
||||
import dan200.computercraft.api.client.TransformedModel;
|
||||
import dan200.computercraft.api.turtle.ITurtleAccess;
|
||||
import dan200.computercraft.api.turtle.ITurtleUpgrade;
|
||||
import dan200.computercraft.api.turtle.TurtleSide;
|
||||
import dan200.computercraft.impl.client.ClientPlatformHelper;
|
||||
import net.minecraft.client.Minecraft;
|
||||
import net.minecraft.core.component.DataComponentPatch;
|
||||
import org.joml.Matrix4f;
|
||||
import org.jspecify.annotations.Nullable;
|
||||
|
||||
final class TurtleUpgradeModellers {
|
||||
private static final Transformation leftTransform = getMatrixFor(-0.4065f);
|
||||
private static final Transformation rightTransform = getMatrixFor(0.4065f);
|
||||
private static final Transformation leftTransform = getMatrixFor(TurtleSide.LEFT);
|
||||
private static final Transformation rightTransform = getMatrixFor(TurtleSide.RIGHT);
|
||||
|
||||
private static Transformation getMatrixFor(float offset) {
|
||||
var matrix = new Matrix4f();
|
||||
matrix.set(new float[]{
|
||||
0.0f, 0.0f, -1.0f, 1.0f + offset,
|
||||
1.0f, 0.0f, 0.0f, 0.0f,
|
||||
0.0f, -1.0f, 0.0f, 1.0f,
|
||||
0.0f, 0.0f, 0.0f, 1.0f,
|
||||
});
|
||||
matrix.transpose();
|
||||
return new Transformation(matrix);
|
||||
private static Transformation getMatrixFor(TurtleSide side) {
|
||||
var pose = new Matrix4f();
|
||||
pose.translate(0.5f, 0.5f, 0.5f);
|
||||
pose.rotate(Axis.YN.rotationDegrees(90f));
|
||||
pose.rotate(Axis.ZP.rotationDegrees(90f));
|
||||
pose.translate(0.0f, 0.0f, side == TurtleSide.RIGHT ? -0.4065f : 0.4065f);
|
||||
return new Transformation(pose);
|
||||
}
|
||||
|
||||
static final TurtleUpgradeModeller<ITurtleUpgrade> UPGRADE_ITEM = new UpgradeItemModeller();
|
||||
@@ -36,10 +32,7 @@ final class TurtleUpgradeModellers {
|
||||
private static final class UpgradeItemModeller implements TurtleUpgradeModeller<ITurtleUpgrade> {
|
||||
@Override
|
||||
public TransformedModel getModel(ITurtleUpgrade upgrade, @Nullable ITurtleAccess turtle, TurtleSide side, DataComponentPatch data) {
|
||||
var stack = upgrade.getUpgradeItem(data);
|
||||
var model = Minecraft.getInstance().getItemRenderer().getItemModelShaper().getItemModel(stack);
|
||||
if (stack.hasFoil()) model = ClientPlatformHelper.get().createdFoiledModel(model);
|
||||
return new TransformedModel(model, side == TurtleSide.LEFT ? leftTransform : rightTransform);
|
||||
return TransformedModel.of(upgrade.getUpgradeItem(data), side == TurtleSide.LEFT ? leftTransform : rightTransform);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -4,12 +4,9 @@
|
||||
|
||||
package dan200.computercraft.impl.client;
|
||||
|
||||
import dan200.computercraft.api.client.ModelLocation;
|
||||
import dan200.computercraft.impl.Services;
|
||||
import net.minecraft.client.renderer.RenderType;
|
||||
import net.minecraft.client.resources.model.BakedModel;
|
||||
import net.minecraft.client.resources.model.ModelManager;
|
||||
import net.minecraft.client.resources.model.ModelResourceLocation;
|
||||
import net.minecraft.resources.ResourceLocation;
|
||||
import org.jetbrains.annotations.ApiStatus;
|
||||
import org.jspecify.annotations.Nullable;
|
||||
@@ -22,33 +19,9 @@ public interface ClientPlatformHelper {
|
||||
* @param manager The model manager.
|
||||
* @param resourceLocation The model resourceLocation.
|
||||
* @return The baked model.
|
||||
* @see ModelLocation
|
||||
*/
|
||||
BakedModel getModel(ModelManager manager, ResourceLocation resourceLocation);
|
||||
|
||||
/**
|
||||
* Set a model from a {@link ModelResourceLocation} or {@link ResourceLocation}.
|
||||
* <p>
|
||||
* This is largely equivalent to {@code resourceLocation == null ? manager.getModel(modelLocation) : getModel(manager, resourceLocation)},
|
||||
* but allows pre-computing {@code modelLocation} (if needed).
|
||||
*
|
||||
* @param manager The model manager.
|
||||
* @param modelLocation The location of the model to load.
|
||||
* @param resourceLocation The location of the resource, if trying to load from a resource.
|
||||
* @return The baked model.
|
||||
* @see ModelLocation
|
||||
*/
|
||||
BakedModel getModel(ModelManager manager, ModelResourceLocation modelLocation, @Nullable ResourceLocation resourceLocation);
|
||||
|
||||
/**
|
||||
* Wrap this model in a version which renders a foil/enchantment glint.
|
||||
*
|
||||
* @param model The model to wrap.
|
||||
* @return The wrapped model.
|
||||
* @see RenderType#glint()
|
||||
*/
|
||||
BakedModel createdFoiledModel(BakedModel model);
|
||||
|
||||
static ClientPlatformHelper get() {
|
||||
var instance = Instance.INSTANCE;
|
||||
return instance == null ? Services.raise(ClientPlatformHelper.class, Instance.ERROR) : instance;
|
||||
|
@@ -11,8 +11,6 @@ import dan200.computercraft.api.lua.GenericSource;
|
||||
import dan200.computercraft.api.lua.IComputerSystem;
|
||||
import dan200.computercraft.api.lua.ILuaAPI;
|
||||
import dan200.computercraft.api.lua.ILuaAPIFactory;
|
||||
import dan200.computercraft.api.media.IMedia;
|
||||
import dan200.computercraft.api.media.MediaProvider;
|
||||
import dan200.computercraft.api.network.PacketNetwork;
|
||||
import dan200.computercraft.api.network.wired.WiredElement;
|
||||
import dan200.computercraft.api.network.wired.WiredNode;
|
||||
@@ -142,19 +140,6 @@ public final class ComputerCraftAPI {
|
||||
return getInstance().getBundledRedstoneOutput(world, pos, side);
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers a media provider to provide {@link IMedia} implementations for Items.
|
||||
*
|
||||
* @param provider The media provider to register.
|
||||
* @see MediaProvider
|
||||
* @deprecated Prefer {@code dan200.computercraft.api.media.MediaLookup} (Fabric) or
|
||||
* {@code dan200.computercraft.api.media.MediaCapability} (NeoForge).
|
||||
*/
|
||||
@Deprecated
|
||||
public static void registerMediaProvider(MediaProvider provider) {
|
||||
getInstance().registerMediaProvider(provider);
|
||||
}
|
||||
|
||||
/**
|
||||
* Attempt to get the game-wide wireless network.
|
||||
*
|
||||
|
@@ -1,26 +0,0 @@
|
||||
// Copyright Daniel Ratcliffe, 2011-2022. This API may be redistributed unmodified and in full only.
|
||||
//
|
||||
// SPDX-License-Identifier: LicenseRef-CCPL
|
||||
|
||||
package dan200.computercraft.api.media;
|
||||
|
||||
import net.minecraft.world.item.ItemStack;
|
||||
import org.jspecify.annotations.Nullable;
|
||||
|
||||
/**
|
||||
* This interface is used to provide {@link IMedia} implementations for {@link ItemStack}.
|
||||
*
|
||||
* @see dan200.computercraft.api.ComputerCraftAPI#registerMediaProvider(MediaProvider)
|
||||
*/
|
||||
@FunctionalInterface
|
||||
public interface MediaProvider {
|
||||
/**
|
||||
* Produce an IMedia implementation from an ItemStack.
|
||||
*
|
||||
* @param stack The stack from which to extract the media information.
|
||||
* @return An {@link IMedia} implementation, or {@code null} if the item is not something you wish to handle
|
||||
* @see dan200.computercraft.api.ComputerCraftAPI#registerMediaProvider(MediaProvider)
|
||||
*/
|
||||
@Nullable
|
||||
IMedia getMedia(ItemStack stack);
|
||||
}
|
@@ -4,17 +4,33 @@
|
||||
|
||||
package dan200.computercraft.api.turtle;
|
||||
|
||||
import com.mojang.serialization.Codec;
|
||||
import net.minecraft.util.StringRepresentable;
|
||||
|
||||
/**
|
||||
* An enum representing the two sides of the turtle that a turtle upgrade might reside.
|
||||
*/
|
||||
public enum TurtleSide {
|
||||
public enum TurtleSide implements StringRepresentable {
|
||||
/**
|
||||
* The turtle's left side (where the pickaxe usually is on a Wireless Mining Turtle).
|
||||
*/
|
||||
LEFT,
|
||||
LEFT("left"),
|
||||
|
||||
/**
|
||||
* The turtle's right side (where the modem usually is on a Wireless Mining Turtle).
|
||||
*/
|
||||
RIGHT,
|
||||
RIGHT("right");
|
||||
|
||||
public static final Codec<TurtleSide> CODEC = StringRepresentable.fromEnum(TurtleSide::values);
|
||||
|
||||
private final String name;
|
||||
|
||||
TurtleSide(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getSerializedName() {
|
||||
return name;
|
||||
}
|
||||
}
|
||||
|
@@ -39,7 +39,7 @@ import java.util.function.Function;
|
||||
* @see ITurtleUpgrade
|
||||
* @see IPocketUpgrade
|
||||
*/
|
||||
public interface UpgradeType<T extends UpgradeBase> {
|
||||
public sealed interface UpgradeType<T extends UpgradeBase> permits UpgradeTypeImpl {
|
||||
/**
|
||||
* The codec to read and write this upgrade from a datapack.
|
||||
*
|
||||
|
@@ -12,7 +12,6 @@ import dan200.computercraft.api.filesystem.Mount;
|
||||
import dan200.computercraft.api.filesystem.WritableMount;
|
||||
import dan200.computercraft.api.lua.GenericSource;
|
||||
import dan200.computercraft.api.lua.ILuaAPIFactory;
|
||||
import dan200.computercraft.api.media.MediaProvider;
|
||||
import dan200.computercraft.api.media.PrintoutContents;
|
||||
import dan200.computercraft.api.network.PacketNetwork;
|
||||
import dan200.computercraft.api.network.wired.WiredElement;
|
||||
@@ -60,8 +59,6 @@ public interface ComputerCraftAPIService {
|
||||
|
||||
int getBundledRedstoneOutput(Level world, BlockPos pos, Direction side);
|
||||
|
||||
void registerMediaProvider(MediaProvider provider);
|
||||
|
||||
PacketNetwork getWirelessNetwork(MinecraftServer server);
|
||||
|
||||
void registerAPIFactory(ILuaAPIFactory factory);
|
||||
|
@@ -11,6 +11,13 @@ plugins {
|
||||
id("cc-tweaked.publishing")
|
||||
}
|
||||
|
||||
sourceSets.client {
|
||||
java {
|
||||
exclude("dan200/computercraft/client/integration/emi")
|
||||
exclude("dan200/computercraft/client/integration/jei")
|
||||
}
|
||||
}
|
||||
|
||||
minecraft {
|
||||
accessWideners(
|
||||
"src/main/resources/computercraft.accesswidener",
|
||||
|
@@ -6,12 +6,13 @@ package dan200.computercraft.client;
|
||||
|
||||
import com.mojang.blaze3d.audio.Channel;
|
||||
import com.mojang.blaze3d.vertex.PoseStack;
|
||||
import com.mojang.math.Axis;
|
||||
import dan200.computercraft.api.turtle.TurtleSide;
|
||||
import dan200.computercraft.client.pocket.ClientPocketComputers;
|
||||
import dan200.computercraft.client.render.CableHighlightRenderer;
|
||||
import dan200.computercraft.client.render.ExtendedItemFrameRenderState;
|
||||
import dan200.computercraft.client.render.PocketItemRenderer;
|
||||
import dan200.computercraft.client.render.PrintoutItemRenderer;
|
||||
import dan200.computercraft.client.render.monitor.MonitorBlockEntityRenderer;
|
||||
import dan200.computercraft.client.render.monitor.MonitorHighlightRenderer;
|
||||
import dan200.computercraft.client.render.monitor.MonitorRenderState;
|
||||
import dan200.computercraft.client.sound.SpeakerManager;
|
||||
@@ -29,11 +30,11 @@ import dan200.computercraft.shared.util.WorldUtil;
|
||||
import net.minecraft.client.Camera;
|
||||
import net.minecraft.client.Minecraft;
|
||||
import net.minecraft.client.renderer.MultiBufferSource;
|
||||
import net.minecraft.client.renderer.entity.state.ItemFrameRenderState;
|
||||
import net.minecraft.client.sounds.AudioStream;
|
||||
import net.minecraft.client.sounds.SoundEngine;
|
||||
import net.minecraft.core.BlockPos;
|
||||
import net.minecraft.world.InteractionHand;
|
||||
import net.minecraft.world.entity.decoration.ItemFrame;
|
||||
import net.minecraft.world.item.ItemStack;
|
||||
import net.minecraft.world.level.block.state.BlockState;
|
||||
import net.minecraft.world.phys.BlockHitResult;
|
||||
@@ -91,9 +92,10 @@ public final class ClientHooks {
|
||||
return false;
|
||||
}
|
||||
|
||||
public static boolean onRenderItemFrame(PoseStack transform, MultiBufferSource render, ItemFrame frame, ItemStack stack, int light) {
|
||||
if (stack.getItem() instanceof PrintoutItem) {
|
||||
PrintoutItemRenderer.onRenderInFrame(transform, render, frame, stack, light);
|
||||
public static boolean onRenderItemFrame(PoseStack transform, MultiBufferSource render, ItemFrameRenderState frame, ExtendedItemFrameRenderState state, int light) {
|
||||
if (state.printoutData != null) {
|
||||
transform.mulPose(Axis.ZP.rotationDegrees(frame.rotation * 360.0f / 8.0f));
|
||||
PrintoutItemRenderer.onRenderInFrame(transform, render, frame, state.printoutData, state.isBook, light);
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -135,17 +137,6 @@ public final class ClientHooks {
|
||||
if (upgrade != null) out.accept(String.format("Upgrade[%s]: %s", side, upgrade.holder().key().location()));
|
||||
}
|
||||
|
||||
/**
|
||||
* Add additional information about the game to the debug screen.
|
||||
*
|
||||
* @param addText A callback which adds a single line of text.
|
||||
*/
|
||||
public static void addGameDebugInfo(Consumer<String> addText) {
|
||||
if (MonitorBlockEntityRenderer.hasRenderedThisFrame() && Minecraft.getInstance().getDebugOverlay().showDebugScreen()) {
|
||||
addText.accept("[CC:T] Monitor renderer: " + MonitorBlockEntityRenderer.currentRenderer());
|
||||
}
|
||||
}
|
||||
|
||||
public static @Nullable BlockState getBlockBreakingState(BlockState state, BlockPos pos) {
|
||||
// Only apply to cables which have both a cable and modem
|
||||
if (state.getBlock() != ModRegistry.Blocks.CABLE.get()
|
||||
|
@@ -8,57 +8,47 @@ import com.mojang.brigadier.CommandDispatcher;
|
||||
import com.mojang.brigadier.arguments.IntegerArgumentType;
|
||||
import com.mojang.brigadier.builder.LiteralArgumentBuilder;
|
||||
import com.mojang.brigadier.builder.RequiredArgumentBuilder;
|
||||
import com.mojang.serialization.MapCodec;
|
||||
import dan200.computercraft.api.ComputerCraftAPI;
|
||||
import dan200.computercraft.api.client.turtle.RegisterTurtleUpgradeModeller;
|
||||
import dan200.computercraft.api.client.turtle.TurtleUpgradeModeller;
|
||||
import dan200.computercraft.client.gui.*;
|
||||
import dan200.computercraft.client.pocket.ClientPocketComputers;
|
||||
import dan200.computercraft.client.item.colour.PocketComputerLight;
|
||||
import dan200.computercraft.client.item.model.TurtleOverlayModel;
|
||||
import dan200.computercraft.client.item.model.TurtleUpgradeModel;
|
||||
import dan200.computercraft.client.item.properties.PocketComputerStateProperty;
|
||||
import dan200.computercraft.client.item.properties.TurtleShowElfOverlay;
|
||||
import dan200.computercraft.client.render.CustomLecternRenderer;
|
||||
import dan200.computercraft.client.render.RenderTypes;
|
||||
import dan200.computercraft.client.render.TurtleBlockEntityRenderer;
|
||||
import dan200.computercraft.client.render.monitor.MonitorBlockEntityRenderer;
|
||||
import dan200.computercraft.client.turtle.TurtleModemModeller;
|
||||
import dan200.computercraft.client.turtle.TurtleUpgradeModellers;
|
||||
import dan200.computercraft.core.util.Colour;
|
||||
import dan200.computercraft.shared.ModRegistry;
|
||||
import dan200.computercraft.shared.command.CommandComputerCraft;
|
||||
import dan200.computercraft.shared.computer.core.ComputerState;
|
||||
import dan200.computercraft.shared.computer.core.ServerContext;
|
||||
import dan200.computercraft.shared.computer.inventory.AbstractComputerMenu;
|
||||
import dan200.computercraft.shared.media.items.DiskItem;
|
||||
import dan200.computercraft.shared.turtle.TurtleOverlay;
|
||||
import net.minecraft.Util;
|
||||
import net.minecraft.client.Minecraft;
|
||||
import net.minecraft.client.color.item.ItemColor;
|
||||
import net.minecraft.client.color.item.ItemTintSource;
|
||||
import net.minecraft.client.gui.screens.MenuScreens;
|
||||
import net.minecraft.client.gui.screens.Screen;
|
||||
import net.minecraft.client.gui.screens.inventory.MenuAccess;
|
||||
import net.minecraft.client.multiplayer.ClientLevel;
|
||||
import net.minecraft.client.renderer.ShaderInstance;
|
||||
import net.minecraft.client.renderer.blockentity.BlockEntityRendererProvider;
|
||||
import net.minecraft.client.renderer.blockentity.BlockEntityRenderers;
|
||||
import net.minecraft.client.renderer.item.ClampedItemPropertyFunction;
|
||||
import net.minecraft.client.renderer.item.ItemProperties;
|
||||
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.network.chat.Component;
|
||||
import net.minecraft.resources.ResourceLocation;
|
||||
import net.minecraft.server.packs.resources.PreparableReloadListener;
|
||||
import net.minecraft.server.packs.resources.ResourceProvider;
|
||||
import net.minecraft.util.FastColor;
|
||||
import net.minecraft.world.entity.LivingEntity;
|
||||
import net.minecraft.world.inventory.AbstractContainerMenu;
|
||||
import net.minecraft.world.inventory.MenuType;
|
||||
import net.minecraft.world.item.Item;
|
||||
import net.minecraft.world.item.ItemStack;
|
||||
import net.minecraft.world.item.component.DyedItemColor;
|
||||
import net.minecraft.world.level.ItemLike;
|
||||
import org.jspecify.annotations.Nullable;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.util.Collection;
|
||||
import java.util.function.BiConsumer;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
/**
|
||||
* Registers client-side objects, such as {@link BlockEntityRendererProvider}s and
|
||||
@@ -83,25 +73,6 @@ public final class ClientRegistry {
|
||||
BlockEntityRenderers.register(ModRegistry.BlockEntities.LECTERN.get(), CustomLecternRenderer::new);
|
||||
}
|
||||
|
||||
/**
|
||||
* Register any client-side objects which must be done on the main thread.
|
||||
*
|
||||
* @param itemProperties Callback to register item properties.
|
||||
*/
|
||||
public static void registerMainThread(RegisterItemProperty itemProperties) {
|
||||
registerItemProperty(itemProperties, "state",
|
||||
new UnclampedPropertyFunction((stack, world, player, random) -> {
|
||||
var computer = ClientPocketComputers.get(stack);
|
||||
return (computer == null ? ComputerState.OFF : computer.getState()).ordinal();
|
||||
}),
|
||||
ModRegistry.Items.POCKET_COMPUTER_NORMAL, ModRegistry.Items.POCKET_COMPUTER_ADVANCED
|
||||
);
|
||||
registerItemProperty(itemProperties, "coloured",
|
||||
(stack, world, player, random) -> DyedItemColor.getOrDefault(stack, -1) != -1 ? 1 : 0,
|
||||
ModRegistry.Items.POCKET_COMPUTER_NORMAL, ModRegistry.Items.POCKET_COMPUTER_ADVANCED
|
||||
);
|
||||
}
|
||||
|
||||
public static void registerMenuScreens(RegisterMenuScreen register) {
|
||||
register.<AbstractComputerMenu, ComputerScreen<AbstractComputerMenu>>register(ModRegistry.Menus.COMPUTER.get(), ComputerScreen::new);
|
||||
register.<AbstractComputerMenu, NoTermComputerScreen<AbstractComputerMenu>>register(ModRegistry.Menus.POCKET_COMPUTER_NO_TERM.get(), NoTermComputerScreen::new);
|
||||
@@ -129,26 +100,14 @@ public final class ClientRegistry {
|
||||
register.register(ModRegistry.TurtleUpgradeTypes.TOOL.get(), TurtleUpgradeModeller.flatItem());
|
||||
}
|
||||
|
||||
@SafeVarargs
|
||||
private static void registerItemProperty(RegisterItemProperty itemProperties, String name, ClampedItemPropertyFunction getter, Supplier<? extends Item>... items) {
|
||||
var id = ResourceLocation.fromNamespaceAndPath(ComputerCraftAPI.MOD_ID, name);
|
||||
for (var item : items) itemProperties.register(item.get(), id, getter);
|
||||
}
|
||||
|
||||
/**
|
||||
* Register an item property via {@link ItemProperties#register}. Forge and Fabric expose different methods, so we
|
||||
* supply this via mod-loader-specific code.
|
||||
*/
|
||||
public interface RegisterItemProperty {
|
||||
void register(Item item, ResourceLocation name, ClampedItemPropertyFunction property);
|
||||
}
|
||||
|
||||
public static void registerReloadListeners(Consumer<PreparableReloadListener> register, Minecraft minecraft) {
|
||||
register.accept(GuiSprites.initialise(minecraft.getTextureManager()));
|
||||
public static void registerReloadListeners(BiConsumer<ResourceLocation, PreparableReloadListener> register, Minecraft minecraft) {
|
||||
register.accept(ResourceLocation.fromNamespaceAndPath(ComputerCraftAPI.MOD_ID, "sprites"), GuiSprites.initialise(minecraft.getTextureManager()));
|
||||
}
|
||||
|
||||
private static final ResourceLocation[] EXTRA_MODELS = {
|
||||
TurtleOverlay.ELF_MODEL,
|
||||
TurtleBlockEntityRenderer.NORMAL_TURTLE_MODEL,
|
||||
TurtleBlockEntityRenderer.ADVANCED_TURTLE_MODEL,
|
||||
TurtleBlockEntityRenderer.COLOUR_TURTLE_MODEL,
|
||||
};
|
||||
|
||||
@@ -158,56 +117,21 @@ public final class ClientRegistry {
|
||||
TurtleUpgradeModellers.getDependencies().forEach(register);
|
||||
}
|
||||
|
||||
public static void registerItemColours(BiConsumer<ItemColor, ItemLike> register) {
|
||||
register.accept(
|
||||
(stack, layer) -> layer == 1 ? DiskItem.getColour(stack) : -1,
|
||||
ModRegistry.Items.DISK.get()
|
||||
);
|
||||
|
||||
register.accept(
|
||||
(stack, layer) -> layer == 1 ? DyedItemColor.getOrDefault(stack, Colour.BLUE.getARGB()) : -1,
|
||||
ModRegistry.Items.TREASURE_DISK.get()
|
||||
);
|
||||
|
||||
register.accept(ClientRegistry::getPocketColour, ModRegistry.Items.POCKET_COMPUTER_NORMAL.get());
|
||||
register.accept(ClientRegistry::getPocketColour, ModRegistry.Items.POCKET_COMPUTER_ADVANCED.get());
|
||||
|
||||
register.accept(ClientRegistry::getTurtleColour, ModRegistry.Blocks.TURTLE_NORMAL.get());
|
||||
register.accept(ClientRegistry::getTurtleColour, ModRegistry.Blocks.TURTLE_ADVANCED.get());
|
||||
public static void registerItemModels(BiConsumer<ResourceLocation, MapCodec<? extends ItemModel.Unbaked>> register) {
|
||||
register.accept(TurtleOverlayModel.ID, TurtleOverlayModel.CODEC);
|
||||
register.accept(TurtleUpgradeModel.ID, TurtleUpgradeModel.CODEC);
|
||||
}
|
||||
|
||||
private static int getPocketColour(ItemStack stack, int layer) {
|
||||
return switch (layer) {
|
||||
default -> -1;
|
||||
case 1 -> DyedItemColor.getOrDefault(stack, -1); // Frame colour
|
||||
case 2 -> { // Light colour
|
||||
var computer = ClientPocketComputers.get(stack);
|
||||
yield computer == null || computer.getLightState() == -1 ? Colour.BLACK.getARGB() : FastColor.ARGB32.opaque(computer.getLightState());
|
||||
}
|
||||
};
|
||||
public static void registerItemColours(BiConsumer<ResourceLocation, MapCodec<? extends ItemTintSource>> register) {
|
||||
register.accept(PocketComputerLight.ID, PocketComputerLight.CODEC);
|
||||
}
|
||||
|
||||
private static int getTurtleColour(ItemStack stack, int layer) {
|
||||
return layer == 0 ? DyedItemColor.getOrDefault(stack, -1) : -1;
|
||||
public static void registerSelectItemProperties(BiConsumer<ResourceLocation, SelectItemModelProperty.Type<?, ?>> register) {
|
||||
register.accept(PocketComputerStateProperty.ID, PocketComputerStateProperty.TYPE);
|
||||
}
|
||||
|
||||
public static void registerShaders(ResourceProvider resources, BiConsumer<ShaderInstance, Consumer<ShaderInstance>> load) throws IOException {
|
||||
RenderTypes.registerShaders(resources, load);
|
||||
}
|
||||
|
||||
private record UnclampedPropertyFunction(
|
||||
ClampedItemPropertyFunction function
|
||||
) implements ClampedItemPropertyFunction {
|
||||
@Override
|
||||
public float unclampedCall(ItemStack stack, @Nullable ClientLevel level, @Nullable LivingEntity entity, int layer) {
|
||||
return function.unclampedCall(stack, level, entity, layer);
|
||||
}
|
||||
|
||||
@Deprecated
|
||||
@Override
|
||||
public float call(ItemStack stack, @Nullable ClientLevel level, @Nullable LivingEntity entity, int layer) {
|
||||
return function.unclampedCall(stack, level, entity, layer);
|
||||
}
|
||||
public static void registerConditionalItemProperties(BiConsumer<ResourceLocation, MapCodec<? extends ConditionalItemModelProperty>> register) {
|
||||
register.accept(TurtleShowElfOverlay.ID, TurtleShowElfOverlay.CODEC);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@@ -99,7 +99,7 @@ public abstract class AbstractComputerScreen<T extends AbstractComputerMenu> ext
|
||||
|
||||
if (uploadNagDeadline != Long.MAX_VALUE && Util.getNanos() >= uploadNagDeadline) {
|
||||
new ItemToast(minecraft(), displayStack, NO_RESPONSE_TITLE, NO_RESPONSE_MSG, ItemToast.TRANSFER_NO_RESPONSE_TOKEN)
|
||||
.showOrReplace(minecraft().getToasts());
|
||||
.showOrReplace(minecraft().getToastManager());
|
||||
uploadNagDeadline = Long.MAX_VALUE;
|
||||
}
|
||||
}
|
||||
|
@@ -7,7 +7,6 @@ package dan200.computercraft.client.gui;
|
||||
import dan200.computercraft.client.gui.widgets.ComputerSidebar;
|
||||
import dan200.computercraft.client.gui.widgets.TerminalWidget;
|
||||
import dan200.computercraft.client.render.ComputerBorderRenderer;
|
||||
import dan200.computercraft.client.render.RenderTypes;
|
||||
import dan200.computercraft.client.render.SpriteRenderer;
|
||||
import dan200.computercraft.shared.computer.inventory.AbstractComputerMenu;
|
||||
import net.minecraft.client.gui.GuiGraphics;
|
||||
@@ -40,14 +39,14 @@ public final class ComputerScreen<T extends AbstractComputerMenu> extends Abstra
|
||||
public void renderBg(GuiGraphics graphics, float partialTicks, int mouseX, int mouseY) {
|
||||
// Draw a border around the terminal
|
||||
var terminal = getTerminal();
|
||||
var spriteRenderer = SpriteRenderer.createForGui(graphics, RenderTypes.GUI_SPRITES);
|
||||
var computerTextures = GuiSprites.getComputerTextures(family);
|
||||
|
||||
ComputerBorderRenderer.render(
|
||||
spriteRenderer, computerTextures,
|
||||
terminal.getX(), terminal.getY(), terminal.getWidth(), terminal.getHeight(), false
|
||||
);
|
||||
ComputerSidebar.renderBackground(spriteRenderer, computerTextures, leftPos, topPos + sidebarYOffset);
|
||||
graphics.flush(); // Flush to ensure background textures are drawn before foreground.
|
||||
SpriteRenderer.inGui(graphics, spriteRenderer -> {
|
||||
var computerTextures = GuiSprites.getComputerTextures(family);
|
||||
ComputerBorderRenderer.render(
|
||||
spriteRenderer, computerTextures,
|
||||
terminal.getX(), terminal.getY(), terminal.getWidth(), terminal.getHeight(), false
|
||||
);
|
||||
ComputerSidebar.renderBackground(spriteRenderer, computerTextures, leftPos, topPos + sidebarYOffset);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@@ -7,6 +7,7 @@ package dan200.computercraft.client.gui;
|
||||
import dan200.computercraft.shared.peripheral.diskdrive.DiskDriveMenu;
|
||||
import net.minecraft.client.gui.GuiGraphics;
|
||||
import net.minecraft.client.gui.screens.inventory.AbstractContainerScreen;
|
||||
import net.minecraft.client.renderer.RenderType;
|
||||
import net.minecraft.network.chat.Component;
|
||||
import net.minecraft.resources.ResourceLocation;
|
||||
import net.minecraft.world.entity.player.Inventory;
|
||||
@@ -23,7 +24,7 @@ public class DiskDriveScreen extends AbstractContainerScreen<DiskDriveMenu> {
|
||||
|
||||
@Override
|
||||
protected void renderBg(GuiGraphics graphics, float partialTicks, int mouseX, int mouseY) {
|
||||
graphics.blit(BACKGROUND, leftPos, topPos, 0, 0, imageWidth, imageHeight);
|
||||
graphics.blit(RenderType::guiTextured, BACKGROUND, leftPos, topPos, 0, 0, imageWidth, imageHeight, 256, 256);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@@ -5,9 +5,11 @@
|
||||
package dan200.computercraft.client.gui;
|
||||
|
||||
import net.minecraft.client.Minecraft;
|
||||
import net.minecraft.client.gui.Font;
|
||||
import net.minecraft.client.gui.GuiGraphics;
|
||||
import net.minecraft.client.gui.components.toasts.Toast;
|
||||
import net.minecraft.client.gui.components.toasts.ToastComponent;
|
||||
import net.minecraft.client.gui.components.toasts.ToastManager;
|
||||
import net.minecraft.client.renderer.RenderType;
|
||||
import net.minecraft.network.chat.Component;
|
||||
import net.minecraft.resources.ResourceLocation;
|
||||
import net.minecraft.util.FormattedCharSequence;
|
||||
@@ -35,8 +37,9 @@ public class ItemToast implements Toast {
|
||||
private final Object token;
|
||||
private final int width;
|
||||
|
||||
private boolean isNew = true;
|
||||
private long firstDisplay;
|
||||
private boolean changed = true;
|
||||
private long lastChanged;
|
||||
private Visibility visibility = Visibility.HIDE;
|
||||
|
||||
public ItemToast(Minecraft minecraft, ItemStack stack, Component title, Component message, Object token) {
|
||||
this.stack = stack;
|
||||
@@ -48,10 +51,10 @@ public class ItemToast implements Toast {
|
||||
width = Math.max(MAX_LINE_SIZE, this.message.stream().mapToInt(font::width).max().orElse(MAX_LINE_SIZE)) + MARGIN * 3 + IMAGE_SIZE;
|
||||
}
|
||||
|
||||
public void showOrReplace(ToastComponent toasts) {
|
||||
public void showOrReplace(ToastManager toasts) {
|
||||
var existing = toasts.getToast(ItemToast.class, getToken());
|
||||
if (existing != null) {
|
||||
existing.isNew = true;
|
||||
existing.changed = true;
|
||||
} else {
|
||||
toasts.addToast(this);
|
||||
}
|
||||
@@ -73,28 +76,22 @@ public class ItemToast implements Toast {
|
||||
}
|
||||
|
||||
@Override
|
||||
public Visibility render(GuiGraphics graphics, ToastComponent component, long time) {
|
||||
if (isNew) {
|
||||
public Visibility getWantedVisibility() {
|
||||
return visibility;
|
||||
}
|
||||
|
||||
firstDisplay = time;
|
||||
isNew = false;
|
||||
@Override
|
||||
public void update(ToastManager toastManager, long time) {
|
||||
if (changed) {
|
||||
lastChanged = time;
|
||||
changed = false;
|
||||
}
|
||||
visibility = time - lastChanged < DISPLAY_TIME * toastManager.getNotificationDisplayTimeMultiplier() ? Visibility.SHOW : Visibility.HIDE;
|
||||
}
|
||||
|
||||
if (width == 160 && message.size() <= 1) {
|
||||
graphics.blitSprite(TEXTURE, 0, 0, width, height());
|
||||
} else {
|
||||
|
||||
var height = height();
|
||||
|
||||
var bottom = Math.min(4, height - 28);
|
||||
renderBackgroundRow(graphics, width, 0, 0, 28);
|
||||
|
||||
for (var i = 28; i < height - bottom; i += 10) {
|
||||
renderBackgroundRow(graphics, width, 16, i, Math.min(16, height - i - bottom));
|
||||
}
|
||||
|
||||
renderBackgroundRow(graphics, width, 32 - bottom, height - bottom, bottom);
|
||||
}
|
||||
@Override
|
||||
public void render(GuiGraphics graphics, Font font, long time) {
|
||||
graphics.blitSprite(RenderType::guiTextured, TEXTURE, 0, 0, width(), height());
|
||||
|
||||
var textX = MARGIN;
|
||||
if (!stack.isEmpty()) {
|
||||
@@ -102,23 +99,9 @@ public class ItemToast implements Toast {
|
||||
graphics.renderFakeItem(stack, MARGIN, MARGIN + height() / 2 - IMAGE_SIZE);
|
||||
}
|
||||
|
||||
graphics.drawString(component.getMinecraft().font, title, textX, MARGIN, 0xff500050, false);
|
||||
graphics.drawString(font, title, textX, MARGIN, 0xff500050, false);
|
||||
for (var i = 0; i < message.size(); ++i) {
|
||||
graphics.drawString(component.getMinecraft().font, message.get(i), textX, LINE_SPACING + (i + 1) * LINE_SPACING, 0xff000000, false);
|
||||
graphics.drawString(font, message.get(i), textX, LINE_SPACING + (i + 1) * LINE_SPACING, 0xff000000, false);
|
||||
}
|
||||
|
||||
return time - firstDisplay < DISPLAY_TIME ? Visibility.SHOW : Visibility.HIDE;
|
||||
}
|
||||
|
||||
private static void renderBackgroundRow(GuiGraphics graphics, int x, int u, int y, int height) {
|
||||
var leftOffset = u == 0 ? 20 : 5;
|
||||
var rightOffset = Math.min(60, x - leftOffset);
|
||||
|
||||
graphics.blitSprite(TEXTURE, 160, 32, 0, u, 0, y, leftOffset, height);
|
||||
for (var k = leftOffset; k < x - rightOffset; k += 64) {
|
||||
graphics.blitSprite(TEXTURE, 160, 32, 32, u, k, y, Math.min(64, x - k - rightOffset), height);
|
||||
}
|
||||
|
||||
graphics.blitSprite(TEXTURE, 160, 32, 160 - rightOffset, u, x - rightOffset, y, rightOffset, height);
|
||||
}
|
||||
}
|
||||
|
@@ -10,6 +10,7 @@ import dan200.computercraft.core.util.Nullability;
|
||||
import dan200.computercraft.shared.computer.inventory.AbstractComputerMenu;
|
||||
import net.minecraft.client.KeyMapping;
|
||||
import net.minecraft.client.Minecraft;
|
||||
import net.minecraft.client.ScrollWheelHandler;
|
||||
import net.minecraft.client.gui.GuiGraphics;
|
||||
import net.minecraft.client.gui.screens.Screen;
|
||||
import net.minecraft.client.gui.screens.inventory.MenuAccess;
|
||||
@@ -32,6 +33,8 @@ public class NoTermComputerScreen<T extends AbstractComputerMenu> extends Screen
|
||||
private final Terminal terminalData;
|
||||
private @Nullable TerminalWidget terminal;
|
||||
|
||||
private final ScrollWheelHandler scrollHandler = new ScrollWheelHandler();
|
||||
|
||||
public NoTermComputerScreen(T menu, Inventory player, Component title) {
|
||||
super(title);
|
||||
this.menu = menu;
|
||||
@@ -67,7 +70,12 @@ public class NoTermComputerScreen<T extends AbstractComputerMenu> extends Screen
|
||||
|
||||
@Override
|
||||
public boolean mouseScrolled(double mouseX, double mouseY, double scrollX, double scrollY) {
|
||||
Objects.requireNonNull(minecraft().player).getInventory().swapPaint(scrollY);
|
||||
var direction = scrollHandler.onMouseScroll(scrollX, scrollY);
|
||||
var inventory = Objects.requireNonNull(minecraft().player).getInventory();
|
||||
inventory.setSelectedHotbarSlot(ScrollWheelHandler.getNextScrollWheelSelection(
|
||||
direction.y == 0 ? -direction.x : direction.y, inventory.selected, Inventory.getSelectionSize()
|
||||
));
|
||||
|
||||
return super.mouseScrolled(mouseX, mouseY, scrollX, scrollY);
|
||||
}
|
||||
|
||||
|
@@ -10,6 +10,7 @@ import net.minecraft.client.gui.components.AbstractWidget;
|
||||
import net.minecraft.client.gui.components.Button;
|
||||
import net.minecraft.client.gui.components.MultiLineLabel;
|
||||
import net.minecraft.client.gui.screens.Screen;
|
||||
import net.minecraft.client.renderer.RenderType;
|
||||
import net.minecraft.network.chat.Component;
|
||||
import net.minecraft.resources.ResourceLocation;
|
||||
import org.jspecify.annotations.Nullable;
|
||||
@@ -87,12 +88,13 @@ public final class OptionScreen extends Screen {
|
||||
@Override
|
||||
public void render(GuiGraphics graphics, int mouseX, int mouseY, float partialTicks) {
|
||||
// Render the actual texture.
|
||||
graphics.blit(BACKGROUND, x, y, 0, 0, innerWidth, PADDING);
|
||||
graphics.blit(BACKGROUND,
|
||||
graphics.blit(RenderType::guiTextured, BACKGROUND, x, y, 0, 0, innerWidth, PADDING, 256, 256);
|
||||
graphics.blit(RenderType::guiTextured, BACKGROUND,
|
||||
x, y + PADDING, 0, PADDING, innerWidth, innerHeight - PADDING * 2,
|
||||
innerWidth, PADDING
|
||||
innerWidth, PADDING,
|
||||
256, 256
|
||||
);
|
||||
graphics.blit(BACKGROUND, x, y + innerHeight - PADDING, 0, 256 - PADDING, innerWidth, PADDING);
|
||||
graphics.blit(RenderType::guiTextured, BACKGROUND, x, y + innerHeight - PADDING, 0, 256 - PADDING, innerWidth, PADDING, 256, 256);
|
||||
|
||||
assertNonNull(messageRenderer).renderLeftAlignedNoShadow(graphics, x + PADDING, y + PADDING, FONT_HEIGHT, 0x404040);
|
||||
super.render(graphics, mouseX, mouseY, partialTicks);
|
||||
|
@@ -7,6 +7,7 @@ package dan200.computercraft.client.gui;
|
||||
import dan200.computercraft.shared.peripheral.printer.PrinterMenu;
|
||||
import net.minecraft.client.gui.GuiGraphics;
|
||||
import net.minecraft.client.gui.screens.inventory.AbstractContainerScreen;
|
||||
import net.minecraft.client.renderer.RenderType;
|
||||
import net.minecraft.network.chat.Component;
|
||||
import net.minecraft.resources.ResourceLocation;
|
||||
import net.minecraft.world.entity.player.Inventory;
|
||||
@@ -23,9 +24,11 @@ public class PrinterScreen extends AbstractContainerScreen<PrinterMenu> {
|
||||
|
||||
@Override
|
||||
protected void renderBg(GuiGraphics graphics, float partialTicks, int mouseX, int mouseY) {
|
||||
graphics.blit(BACKGROUND, leftPos, topPos, 0, 0, imageWidth, imageHeight);
|
||||
graphics.blit(RenderType::guiTextured, BACKGROUND, leftPos, topPos, 0, 0, imageWidth, imageHeight, 256, 256);
|
||||
|
||||
if (getMenu().isPrinting()) graphics.blit(BACKGROUND, leftPos + 34, topPos + 21, 176, 0, 25, 45);
|
||||
if (getMenu().isPrinting()) {
|
||||
graphics.blit(RenderType::guiTextured, BACKGROUND, leftPos + 34, topPos + 21, 176, 0, 25, 45, 256, 256);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@@ -10,6 +10,7 @@ import dan200.computercraft.shared.media.PrintoutMenu;
|
||||
import dan200.computercraft.shared.media.items.PrintoutData;
|
||||
import net.minecraft.client.gui.GuiGraphics;
|
||||
import net.minecraft.client.gui.screens.inventory.AbstractContainerScreen;
|
||||
import net.minecraft.client.renderer.LightTexture;
|
||||
import net.minecraft.network.chat.Component;
|
||||
import net.minecraft.world.entity.player.Inventory;
|
||||
import net.minecraft.world.inventory.AbstractContainerMenu;
|
||||
@@ -20,7 +21,6 @@ import org.lwjgl.glfw.GLFW;
|
||||
import java.util.Objects;
|
||||
|
||||
import static dan200.computercraft.client.render.PrintoutRenderer.*;
|
||||
import static dan200.computercraft.client.render.RenderTypes.FULL_BRIGHT_LIGHTMAP;
|
||||
|
||||
/**
|
||||
* The GUI for printed pages and books.
|
||||
@@ -116,8 +116,10 @@ public final class PrintoutScreen extends AbstractContainerScreen<PrintoutMenu>
|
||||
graphics.pose().pushPose();
|
||||
graphics.pose().translate(0, 0, 1);
|
||||
|
||||
drawBorder(graphics.pose(), graphics.bufferSource(), leftPos, topPos, 0, page, printout.pages(), printout.book(), FULL_BRIGHT_LIGHTMAP);
|
||||
drawText(graphics.pose(), graphics.bufferSource(), leftPos + X_TEXT_MARGIN, topPos + Y_TEXT_MARGIN, PrintoutData.LINES_PER_PAGE * page, FULL_BRIGHT_LIGHTMAP, printout.text(), printout.colour());
|
||||
graphics.drawSpecial(bufferSource -> {
|
||||
drawBorder(graphics.pose(), bufferSource, leftPos, topPos, 0, page, printout.pages(), printout.book(), LightTexture.FULL_BRIGHT);
|
||||
drawText(graphics.pose(), bufferSource, leftPos + X_TEXT_MARGIN, topPos + Y_TEXT_MARGIN, PrintoutData.LINES_PER_PAGE * page, LightTexture.FULL_BRIGHT, printout.text(), printout.colour());
|
||||
});
|
||||
|
||||
graphics.pose().popPose();
|
||||
}
|
||||
|
@@ -7,12 +7,12 @@ package dan200.computercraft.client.gui;
|
||||
import dan200.computercraft.api.ComputerCraftAPI;
|
||||
import dan200.computercraft.client.gui.widgets.ComputerSidebar;
|
||||
import dan200.computercraft.client.gui.widgets.TerminalWidget;
|
||||
import dan200.computercraft.client.render.RenderTypes;
|
||||
import dan200.computercraft.client.render.SpriteRenderer;
|
||||
import dan200.computercraft.shared.computer.core.ComputerFamily;
|
||||
import dan200.computercraft.shared.computer.inventory.AbstractComputerMenu;
|
||||
import dan200.computercraft.shared.turtle.inventory.TurtleMenu;
|
||||
import net.minecraft.client.gui.GuiGraphics;
|
||||
import net.minecraft.client.renderer.RenderType;
|
||||
import net.minecraft.network.chat.Component;
|
||||
import net.minecraft.resources.ResourceLocation;
|
||||
import net.minecraft.world.entity.player.Inventory;
|
||||
@@ -49,8 +49,11 @@ public class TurtleScreen extends AbstractComputerScreen<TurtleMenu> {
|
||||
@Override
|
||||
protected void renderBg(GuiGraphics graphics, float partialTicks, int mouseX, int mouseY) {
|
||||
var advanced = family == ComputerFamily.ADVANCED;
|
||||
var texture = advanced ? BACKGROUND_ADVANCED : BACKGROUND_NORMAL;
|
||||
graphics.blit(texture, leftPos + AbstractComputerMenu.SIDEBAR_WIDTH, topPos, 0, 0, 0, TEX_WIDTH, TEX_HEIGHT, FULL_TEX_SIZE, FULL_TEX_SIZE);
|
||||
graphics.blit(
|
||||
RenderType::guiTextured, advanced ? BACKGROUND_ADVANCED : BACKGROUND_NORMAL,
|
||||
leftPos + AbstractComputerMenu.SIDEBAR_WIDTH, topPos, 0, 0,
|
||||
TEX_WIDTH, TEX_HEIGHT, FULL_TEX_SIZE, FULL_TEX_SIZE
|
||||
);
|
||||
|
||||
// Render selected slot
|
||||
var slot = getMenu().getSelectedSlot();
|
||||
@@ -58,14 +61,14 @@ public class TurtleScreen extends AbstractComputerScreen<TurtleMenu> {
|
||||
var slotX = slot % 4;
|
||||
var slotY = slot / 4;
|
||||
graphics.blitSprite(
|
||||
advanced ? SELECTED_ADVANCED : SELECTED_NORMAL,
|
||||
leftPos + TURTLE_START_X - 2 + slotX * 18, topPos + PLAYER_START_Y - 2 + slotY * 18, 0, 22, 22
|
||||
RenderType::guiTextured, advanced ? SELECTED_ADVANCED : SELECTED_NORMAL,
|
||||
leftPos + TURTLE_START_X - 2 + slotX * 18, topPos + PLAYER_START_Y - 2 + slotY * 18, 22, 22
|
||||
);
|
||||
}
|
||||
|
||||
// Render sidebar
|
||||
var spriteRenderer = SpriteRenderer.createForGui(graphics, RenderTypes.GUI_SPRITES);
|
||||
ComputerSidebar.renderBackground(spriteRenderer, GuiSprites.getComputerTextures(family), leftPos, topPos + sidebarYOffset);
|
||||
graphics.flush(); // Flush to ensure background textures are drawn before foreground.
|
||||
SpriteRenderer.inGui(graphics, spriteRenderer ->
|
||||
ComputerSidebar.renderBackground(spriteRenderer, GuiSprites.getComputerTextures(family), leftPos, topPos + sidebarYOffset)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@@ -4,12 +4,12 @@
|
||||
|
||||
package dan200.computercraft.client.gui.widgets;
|
||||
|
||||
import com.mojang.blaze3d.systems.RenderSystem;
|
||||
import it.unimi.dsi.fastutil.booleans.Boolean2ObjectFunction;
|
||||
import net.minecraft.ChatFormatting;
|
||||
import net.minecraft.client.gui.GuiGraphics;
|
||||
import net.minecraft.client.gui.components.Button;
|
||||
import net.minecraft.client.gui.components.Tooltip;
|
||||
import net.minecraft.client.renderer.RenderType;
|
||||
import net.minecraft.network.chat.Component;
|
||||
import net.minecraft.resources.ResourceLocation;
|
||||
import org.jspecify.annotations.Nullable;
|
||||
@@ -48,10 +48,7 @@ public class DynamicImageButton extends Button {
|
||||
setTooltip(message.tooltip());
|
||||
|
||||
var texture = this.texture.get(isHoveredOrFocused());
|
||||
|
||||
RenderSystem.disableDepthTest();
|
||||
graphics.blitSprite(texture, getX(), getY(), 0, width, height);
|
||||
RenderSystem.enableDepthTest();
|
||||
graphics.blitSprite(RenderType::guiTextured, texture, getX(), getY(), width, height);
|
||||
}
|
||||
|
||||
public record HintedMessage(Component message, Tooltip tooltip) {
|
||||
|
@@ -4,7 +4,6 @@
|
||||
|
||||
package dan200.computercraft.client.gui.widgets;
|
||||
|
||||
import dan200.computercraft.client.render.RenderTypes;
|
||||
import dan200.computercraft.client.render.text.FixedWidthFontRenderer;
|
||||
import dan200.computercraft.core.terminal.Terminal;
|
||||
import dan200.computercraft.core.util.StringUtil;
|
||||
@@ -254,12 +253,12 @@ public class TerminalWidget extends AbstractWidget {
|
||||
public void renderWidget(GuiGraphics graphics, int mouseX, int mouseY, float partialTicks) {
|
||||
if (!visible) return;
|
||||
|
||||
var emitter = FixedWidthFontRenderer.toVertexConsumer(graphics.pose(), graphics.bufferSource().getBuffer(RenderTypes.TERMINAL));
|
||||
|
||||
FixedWidthFontRenderer.drawTerminal(
|
||||
emitter,
|
||||
(float) innerX, (float) innerY, terminal, (float) MARGIN, (float) MARGIN, (float) MARGIN, (float) MARGIN
|
||||
);
|
||||
graphics.drawSpecial(bufferSource -> {
|
||||
FixedWidthFontRenderer.drawTerminal(
|
||||
FixedWidthFontRenderer.toVertexConsumer(graphics.pose(), bufferSource.getBuffer(FixedWidthFontRenderer.TERMINAL_TEXT)),
|
||||
(float) innerX, (float) innerY, terminal, (float) MARGIN, (float) MARGIN, (float) MARGIN, (float) MARGIN
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@@ -5,17 +5,18 @@
|
||||
package dan200.computercraft.client.integration;
|
||||
|
||||
import com.google.auto.service.AutoService;
|
||||
import com.mojang.blaze3d.vertex.ByteBufferBuilder;
|
||||
import com.mojang.blaze3d.vertex.VertexFormat;
|
||||
import dan200.computercraft.client.render.RenderTypes;
|
||||
import dan200.computercraft.client.render.text.DirectFixedWidthFontRenderer;
|
||||
import dan200.computercraft.shared.platform.PlatformHelper;
|
||||
import net.irisshaders.iris.api.v0.IrisApi;
|
||||
import net.irisshaders.iris.api.v0.IrisTextVertexSink;
|
||||
import net.minecraft.util.FastColor;
|
||||
import net.minecraft.client.renderer.LightTexture;
|
||||
import org.lwjgl.system.MemoryUtil;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.Optional;
|
||||
import java.util.function.IntFunction;
|
||||
|
||||
@AutoService(ShaderMod.Provider.class)
|
||||
public class IrisShaderMod implements ShaderMod.Provider {
|
||||
@@ -31,7 +32,7 @@ public class IrisShaderMod implements ShaderMod.Provider {
|
||||
}
|
||||
|
||||
@Override
|
||||
public DirectFixedWidthFontRenderer.QuadEmitter getQuadEmitter(int vertexCount, IntFunction<ByteBuffer> makeBuffer) {
|
||||
public DirectFixedWidthFontRenderer.QuadEmitter getQuadEmitter(int vertexCount, ByteBufferBuilder makeBuffer) {
|
||||
return IrisApi.getInstance().getMinorApiRevision() >= 1
|
||||
? new IrisQuadEmitter(vertexCount, makeBuffer)
|
||||
: super.getQuadEmitter(vertexCount, makeBuffer);
|
||||
@@ -39,25 +40,24 @@ public class IrisShaderMod implements ShaderMod.Provider {
|
||||
|
||||
private static final class IrisQuadEmitter implements DirectFixedWidthFontRenderer.QuadEmitter {
|
||||
private final IrisTextVertexSink sink;
|
||||
private @Nullable ByteBuffer buffer;
|
||||
|
||||
private IrisQuadEmitter(int vertexCount, IntFunction<ByteBuffer> makeBuffer) {
|
||||
sink = IrisApi.getInstance().createTextVertexSink(vertexCount, makeBuffer);
|
||||
private IrisQuadEmitter(int vertexCount, ByteBufferBuilder builder) {
|
||||
sink = IrisApi.getInstance().createTextVertexSink(vertexCount, i -> {
|
||||
if (buffer != null) throw new IllegalStateException("Allocated multiple buffers");
|
||||
return buffer = MemoryUtil.memByteBuffer(builder.reserve(i), i);
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void quad(float x1, float y1, float x2, float y2, float z, int colour, float u1, float v1, float u2, float v2) {
|
||||
sink.quad(x1, y1, x2, y2, z, colour, u1, v1, u2, v2, LightTexture.FULL_BRIGHT);
|
||||
}
|
||||
|
||||
@Override
|
||||
public VertexFormat format() {
|
||||
return sink.getUnderlyingVertexFormat();
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuffer buffer() {
|
||||
return sink.getUnderlyingByteBuffer();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void quad(float x1, float y1, float x2, float y2, float z, int colour, float u1, float v1, float u2, float v2) {
|
||||
sink.quad(x1, y1, x2, y2, z, FastColor.ABGR32.fromArgb32(colour), u1, v1, u2, v2, RenderTypes.FULL_BRIGHT_LIGHTMAP);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -4,14 +4,12 @@
|
||||
|
||||
package dan200.computercraft.client.integration;
|
||||
|
||||
import dan200.computercraft.client.render.RenderTypes;
|
||||
import com.mojang.blaze3d.vertex.ByteBufferBuilder;
|
||||
import com.mojang.blaze3d.vertex.VertexBuffer;
|
||||
import dan200.computercraft.client.render.text.DirectFixedWidthFontRenderer;
|
||||
import dan200.computercraft.client.render.vbo.DirectVertexBuffer;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.Optional;
|
||||
import java.util.ServiceLoader;
|
||||
import java.util.function.IntFunction;
|
||||
|
||||
/**
|
||||
* Find the currently loaded shader mod (if present) and provides utilities for interacting with it.
|
||||
@@ -31,16 +29,14 @@ public class ShaderMod {
|
||||
}
|
||||
|
||||
/**
|
||||
* Get an appropriate quad emitter for use with {@link DirectVertexBuffer} and {@link DirectFixedWidthFontRenderer} .
|
||||
* Get an appropriate quad emitter for use with {@link VertexBuffer} and {@link DirectFixedWidthFontRenderer} .
|
||||
*
|
||||
* @param vertexCount The number of vertices.
|
||||
* @param makeBuffer A function to allocate a temporary buffer.
|
||||
* @param buffer A function to allocate a temporary buffer.
|
||||
* @return The quad emitter.
|
||||
*/
|
||||
public DirectFixedWidthFontRenderer.QuadEmitter getQuadEmitter(int vertexCount, IntFunction<ByteBuffer> makeBuffer) {
|
||||
return new DirectFixedWidthFontRenderer.ByteBufferEmitter(
|
||||
makeBuffer.apply(RenderTypes.TERMINAL.format().getVertexSize() * vertexCount * 4)
|
||||
);
|
||||
public DirectFixedWidthFontRenderer.QuadEmitter getQuadEmitter(int vertexCount, ByteBufferBuilder buffer) {
|
||||
return new DirectFixedWidthFontRenderer.ByteBufferEmitter(buffer);
|
||||
}
|
||||
|
||||
public interface Provider {
|
||||
|
@@ -8,7 +8,6 @@ import dan200.computercraft.api.ComputerCraftAPI;
|
||||
import dan200.computercraft.api.turtle.TurtleSide;
|
||||
import dan200.computercraft.shared.ModRegistry;
|
||||
import dan200.computercraft.shared.integration.RecipeModHelpers;
|
||||
import dan200.computercraft.shared.media.items.DiskItem;
|
||||
import dan200.computercraft.shared.pocket.items.PocketComputerItem;
|
||||
import dan200.computercraft.shared.turtle.items.TurtleItem;
|
||||
import mezz.jei.api.IModPlugin;
|
||||
@@ -23,6 +22,7 @@ import net.minecraft.client.Minecraft;
|
||||
import net.minecraft.core.RegistryAccess;
|
||||
import net.minecraft.resources.ResourceLocation;
|
||||
import net.minecraft.world.item.ItemStack;
|
||||
import net.minecraft.world.item.component.DyedItemColor;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@@ -100,7 +100,7 @@ public class JEIComputerCraft implements IModPlugin {
|
||||
/**
|
||||
* Distinguishes disks by colour.
|
||||
*/
|
||||
private static final IIngredientSubtypeInterpreter<ItemStack> diskSubtype = (stack, ctx) -> Integer.toString(DiskItem.getColour(stack));
|
||||
private static final IIngredientSubtypeInterpreter<ItemStack> diskSubtype = (stack, ctx) -> Integer.toString(DyedItemColor.getOrDefault(stack, -1));
|
||||
|
||||
private static RegistryAccess getRegistryAccess() {
|
||||
return Minecraft.getInstance().level.registryAccess();
|
||||
|
@@ -0,0 +1,43 @@
|
||||
// SPDX-FileCopyrightText: 2025 The CC: Tweaked Developers
|
||||
//
|
||||
// SPDX-License-Identifier: MPL-2.0
|
||||
|
||||
package dan200.computercraft.client.item.colour;
|
||||
|
||||
import com.mojang.serialization.MapCodec;
|
||||
import com.mojang.serialization.codecs.RecordCodecBuilder;
|
||||
import dan200.computercraft.api.ComputerCraftAPI;
|
||||
import dan200.computercraft.client.pocket.ClientPocketComputers;
|
||||
import dan200.computercraft.client.pocket.PocketComputerData;
|
||||
import net.minecraft.client.color.item.ItemTintSource;
|
||||
import net.minecraft.client.multiplayer.ClientLevel;
|
||||
import net.minecraft.resources.ResourceLocation;
|
||||
import net.minecraft.util.ARGB;
|
||||
import net.minecraft.util.ExtraCodecs;
|
||||
import net.minecraft.world.entity.LivingEntity;
|
||||
import net.minecraft.world.item.ItemStack;
|
||||
import org.jspecify.annotations.Nullable;
|
||||
|
||||
/**
|
||||
* An {@link ItemTintSource} that returns the pocket computer's {@linkplain PocketComputerData#getLightState() light
|
||||
* colour}.
|
||||
*
|
||||
* @param defaultColour The default colour, if the light is not currently on.
|
||||
*/
|
||||
public record PocketComputerLight(int defaultColour) implements ItemTintSource {
|
||||
public static final ResourceLocation ID = ResourceLocation.fromNamespaceAndPath(ComputerCraftAPI.MOD_ID, "pocket_computer_light");
|
||||
public static final MapCodec<PocketComputerLight> CODEC = RecordCodecBuilder.mapCodec(instance -> instance.group(
|
||||
ExtraCodecs.RGB_COLOR_CODEC.fieldOf("default").forGetter(PocketComputerLight::defaultColour)
|
||||
).apply(instance, PocketComputerLight::new));
|
||||
|
||||
@Override
|
||||
public int calculate(ItemStack stack, @Nullable ClientLevel level, @Nullable LivingEntity holder) {
|
||||
var computer = ClientPocketComputers.get(stack);
|
||||
return computer == null || computer.getLightState() == -1 ? defaultColour : ARGB.opaque(computer.getLightState());
|
||||
}
|
||||
|
||||
@Override
|
||||
public MapCodec<? extends ItemTintSource> type() {
|
||||
return CODEC;
|
||||
}
|
||||
}
|
@@ -0,0 +1,32 @@
|
||||
// SPDX-FileCopyrightText: 2025 The CC: Tweaked Developers
|
||||
//
|
||||
// SPDX-License-Identifier: MPL-2.0
|
||||
|
||||
package dan200.computercraft.client.item.model;
|
||||
|
||||
import net.minecraft.client.renderer.Sheets;
|
||||
import net.minecraft.client.renderer.block.model.ItemTransforms;
|
||||
import net.minecraft.client.renderer.item.ItemStackRenderState;
|
||||
import net.minecraft.client.resources.model.BakedModel;
|
||||
import net.minecraft.client.resources.model.DelegateBakedModel;
|
||||
|
||||
/**
|
||||
* A {@link BakedModel} that wraps another model, but providing different {@link ItemTransforms}.
|
||||
*/
|
||||
class BakedModelWithTransform extends DelegateBakedModel {
|
||||
private final ItemTransforms transforms;
|
||||
|
||||
BakedModelWithTransform(BakedModel bakedModel, ItemTransforms transforms) {
|
||||
super(bakedModel);
|
||||
this.transforms = transforms;
|
||||
}
|
||||
|
||||
static void addLayer(ItemStackRenderState state, BakedModel model, ItemTransforms transforms) {
|
||||
state.newLayer().setupBlockModel(new BakedModelWithTransform(model, transforms), Sheets.translucentItemSheet());
|
||||
}
|
||||
|
||||
@Override
|
||||
public ItemTransforms getTransforms() {
|
||||
return transforms;
|
||||
}
|
||||
}
|
@@ -0,0 +1,61 @@
|
||||
// SPDX-FileCopyrightText: 2025 The CC: Tweaked Developers
|
||||
//
|
||||
// SPDX-License-Identifier: MPL-2.0
|
||||
|
||||
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.platform.ClientPlatformHelper;
|
||||
import dan200.computercraft.shared.turtle.TurtleOverlay;
|
||||
import dan200.computercraft.shared.turtle.items.TurtleItem;
|
||||
import net.minecraft.client.Minecraft;
|
||||
import net.minecraft.client.multiplayer.ClientLevel;
|
||||
import net.minecraft.client.renderer.block.model.ItemTransforms;
|
||||
import net.minecraft.client.renderer.item.ItemModel;
|
||||
import net.minecraft.client.renderer.item.ItemModelResolver;
|
||||
import net.minecraft.client.renderer.item.ItemStackRenderState;
|
||||
import net.minecraft.resources.ResourceLocation;
|
||||
import net.minecraft.world.entity.LivingEntity;
|
||||
import net.minecraft.world.item.ItemDisplayContext;
|
||||
import net.minecraft.world.item.ItemStack;
|
||||
import org.jspecify.annotations.Nullable;
|
||||
|
||||
/**
|
||||
* An {@link ItemModel} that renders the {@linkplain TurtleOverlay turtle overlay}.
|
||||
*
|
||||
* @param transforms The item transformations from the base model.
|
||||
* @see TurtleOverlay#model()
|
||||
*/
|
||||
public record TurtleOverlayModel(ItemTransforms transforms) implements ItemModel {
|
||||
public static final ResourceLocation ID = ResourceLocation.fromNamespaceAndPath(ComputerCraftAPI.MOD_ID, "turtle/overlay");
|
||||
public static final MapCodec<Unbaked> CODEC = RecordCodecBuilder.mapCodec(instance -> instance.group(
|
||||
ResourceLocation.CODEC.fieldOf("transforms").forGetter(Unbaked::base)
|
||||
).apply(instance, Unbaked::new));
|
||||
|
||||
@Override
|
||||
public void update(ItemStackRenderState state, ItemStack stack, ItemModelResolver resolver, ItemDisplayContext context, @Nullable ClientLevel level, @Nullable LivingEntity holder, int light) {
|
||||
var overlay = TurtleItem.getOverlay(stack);
|
||||
if (overlay == null) return;
|
||||
|
||||
var model = ClientPlatformHelper.get().getModel(Minecraft.getInstance().getModelManager(), overlay.model());
|
||||
BakedModelWithTransform.addLayer(state, model, transforms());
|
||||
}
|
||||
|
||||
public record Unbaked(ResourceLocation base) implements ItemModel.Unbaked {
|
||||
@Override
|
||||
public MapCodec<Unbaked> type() {
|
||||
return CODEC;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ItemModel bake(BakingContext bakingContext) {
|
||||
return new TurtleOverlayModel(bakingContext.bake(base).getTransforms());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void resolveDependencies(Resolver resolver) {
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,94 @@
|
||||
// SPDX-FileCopyrightText: 2025 The CC: Tweaked Developers
|
||||
//
|
||||
// SPDX-License-Identifier: MPL-2.0
|
||||
|
||||
package dan200.computercraft.client.item.model;
|
||||
|
||||
import com.mojang.blaze3d.vertex.PoseStack;
|
||||
import com.mojang.math.Transformation;
|
||||
import com.mojang.serialization.MapCodec;
|
||||
import com.mojang.serialization.codecs.RecordCodecBuilder;
|
||||
import dan200.computercraft.api.ComputerCraftAPI;
|
||||
import dan200.computercraft.api.client.TransformedModel;
|
||||
import dan200.computercraft.api.client.turtle.TurtleUpgradeModeller;
|
||||
import dan200.computercraft.api.turtle.TurtleSide;
|
||||
import dan200.computercraft.client.turtle.TurtleUpgradeModellers;
|
||||
import dan200.computercraft.shared.turtle.items.TurtleItem;
|
||||
import net.minecraft.client.multiplayer.ClientLevel;
|
||||
import net.minecraft.client.renderer.MultiBufferSource;
|
||||
import net.minecraft.client.renderer.item.ItemModel;
|
||||
import net.minecraft.client.renderer.item.ItemModelResolver;
|
||||
import net.minecraft.client.renderer.item.ItemStackRenderState;
|
||||
import net.minecraft.client.renderer.special.SpecialModelRenderer;
|
||||
import net.minecraft.client.resources.model.BakedModel;
|
||||
import net.minecraft.resources.ResourceLocation;
|
||||
import net.minecraft.world.entity.LivingEntity;
|
||||
import net.minecraft.world.item.ItemDisplayContext;
|
||||
import net.minecraft.world.item.ItemStack;
|
||||
import org.jspecify.annotations.Nullable;
|
||||
|
||||
/**
|
||||
* An {@link ItemModel} that renders a turtle upgrade, using its {@link TurtleUpgradeModeller}.
|
||||
*
|
||||
* @param side The side the upgrade resides on.
|
||||
* @param base The base model. Only used to provide item transforms.
|
||||
*/
|
||||
public record TurtleUpgradeModel(TurtleSide side, BakedModel base) implements ItemModel {
|
||||
public static final ResourceLocation ID = ResourceLocation.fromNamespaceAndPath(ComputerCraftAPI.MOD_ID, "turtle/upgrade");
|
||||
public static final MapCodec<Unbaked> CODEC = RecordCodecBuilder.mapCodec(instance -> instance.group(
|
||||
TurtleSide.CODEC.fieldOf("side").forGetter(Unbaked::side),
|
||||
ResourceLocation.CODEC.fieldOf("transforms").forGetter(Unbaked::base)
|
||||
).apply(instance, Unbaked::new));
|
||||
|
||||
@Override
|
||||
public void update(ItemStackRenderState state, ItemStack stack, ItemModelResolver resolver, ItemDisplayContext context, @Nullable ClientLevel level, @Nullable LivingEntity holder, int light) {
|
||||
var upgrade = TurtleItem.getUpgradeWithData(stack, side);
|
||||
if (upgrade == null) return;
|
||||
|
||||
switch (TurtleUpgradeModellers.getModel(upgrade.upgrade(), upgrade.data(), side)) {
|
||||
case TransformedModel.Item model -> {
|
||||
var childState = new ItemStackRenderState();
|
||||
resolver.updateForTopItem(childState, model.stack(), ItemDisplayContext.NONE, false, level, null, 0);
|
||||
if (!childState.isEmpty()) {
|
||||
state.newLayer().setupSpecialModel(new TransformedRenderer(childState, model.transformation()), null, base);
|
||||
}
|
||||
}
|
||||
case TransformedModel.Baked baked ->
|
||||
BakedModelWithTransform.addLayer(state, baked.model(), base.getTransforms());
|
||||
}
|
||||
}
|
||||
|
||||
public record Unbaked(TurtleSide side, ResourceLocation base) implements ItemModel.Unbaked {
|
||||
@Override
|
||||
public MapCodec<Unbaked> type() {
|
||||
return CODEC;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ItemModel bake(BakingContext bakingContext) {
|
||||
return new TurtleUpgradeModel(side, bakingContext.bake(base));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void resolveDependencies(Resolver resolver) {
|
||||
resolver.resolve(base);
|
||||
}
|
||||
}
|
||||
|
||||
private record TransformedRenderer(
|
||||
ItemStackRenderState state, Transformation transform
|
||||
) implements SpecialModelRenderer<Void> {
|
||||
@Override
|
||||
public void render(@Nullable Void object, ItemDisplayContext itemDisplayContext, PoseStack poseStack, MultiBufferSource multiBufferSource, int overlay, int light, boolean bl) {
|
||||
poseStack.pushPose();
|
||||
poseStack.mulPose(transform.getMatrix());
|
||||
state.render(poseStack, multiBufferSource, overlay, light);
|
||||
poseStack.popPose();
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nullable Void extractArgument(ItemStack itemStack) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,45 @@
|
||||
// SPDX-FileCopyrightText: 2025 The CC: Tweaked Developers
|
||||
//
|
||||
// SPDX-License-Identifier: MPL-2.0
|
||||
|
||||
package dan200.computercraft.client.item.properties;
|
||||
|
||||
import com.mojang.serialization.MapCodec;
|
||||
import dan200.computercraft.api.ComputerCraftAPI;
|
||||
import dan200.computercraft.client.pocket.ClientPocketComputers;
|
||||
import dan200.computercraft.shared.computer.core.ComputerState;
|
||||
import net.minecraft.client.multiplayer.ClientLevel;
|
||||
import net.minecraft.client.renderer.item.properties.select.SelectItemModelProperty;
|
||||
import net.minecraft.resources.ResourceLocation;
|
||||
import net.minecraft.world.entity.LivingEntity;
|
||||
import net.minecraft.world.item.ItemDisplayContext;
|
||||
import net.minecraft.world.item.ItemStack;
|
||||
import org.jspecify.annotations.Nullable;
|
||||
|
||||
/**
|
||||
* A {@link SelectItemModelProperty} that returns the pocket computer's current state.
|
||||
*/
|
||||
public final class PocketComputerStateProperty implements SelectItemModelProperty<ComputerState> {
|
||||
public static final ResourceLocation ID = ResourceLocation.fromNamespaceAndPath(ComputerCraftAPI.MOD_ID, "pocket_computer_state");
|
||||
private static final PocketComputerStateProperty INSTANCE = new PocketComputerStateProperty();
|
||||
public static final MapCodec<PocketComputerStateProperty> CODEC = MapCodec.unit(INSTANCE);
|
||||
public static final Type<PocketComputerStateProperty, ComputerState> TYPE = Type.create(CODEC, ComputerState.CODEC);
|
||||
|
||||
private PocketComputerStateProperty() {
|
||||
}
|
||||
|
||||
public static PocketComputerStateProperty create() {
|
||||
return INSTANCE;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ComputerState get(ItemStack stack, @Nullable ClientLevel level, @Nullable LivingEntity holder, int i, ItemDisplayContext context) {
|
||||
var computer = ClientPocketComputers.get(stack);
|
||||
return computer == null ? ComputerState.OFF : computer.getState();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Type<? extends SelectItemModelProperty<ComputerState>, ComputerState> type() {
|
||||
return TYPE;
|
||||
}
|
||||
}
|
@@ -0,0 +1,44 @@
|
||||
// SPDX-FileCopyrightText: 2025 The CC: Tweaked Developers
|
||||
//
|
||||
// SPDX-License-Identifier: MPL-2.0
|
||||
|
||||
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.shared.turtle.items.TurtleItem;
|
||||
import net.minecraft.client.multiplayer.ClientLevel;
|
||||
import net.minecraft.client.renderer.item.properties.conditional.ConditionalItemModelProperty;
|
||||
import net.minecraft.resources.ResourceLocation;
|
||||
import net.minecraft.world.entity.LivingEntity;
|
||||
import net.minecraft.world.item.ItemDisplayContext;
|
||||
import net.minecraft.world.item.ItemStack;
|
||||
import org.jspecify.annotations.Nullable;
|
||||
|
||||
/**
|
||||
* An item property that determines whether the turtle's current {@linkplain TurtleOverlay overlay} is compatible
|
||||
* with the Christmas overlay.
|
||||
*
|
||||
* @see TurtleOverlay#showElfOverlay()
|
||||
*/
|
||||
public class TurtleShowElfOverlay implements ConditionalItemModelProperty {
|
||||
public static final ResourceLocation ID = ResourceLocation.fromNamespaceAndPath(ComputerCraftAPI.MOD_ID, "turtle/show_elf_overlay");
|
||||
private static final TurtleShowElfOverlay INSTANCE = new TurtleShowElfOverlay();
|
||||
public static final MapCodec<TurtleShowElfOverlay> CODEC = MapCodec.unit(INSTANCE);
|
||||
|
||||
@Override
|
||||
public boolean get(ItemStack stack, @Nullable ClientLevel level, @Nullable LivingEntity holder, int i, ItemDisplayContext context) {
|
||||
var overlay = TurtleItem.getOverlay(stack);
|
||||
return overlay == null || overlay.showElfOverlay();
|
||||
}
|
||||
|
||||
public static TurtleShowElfOverlay create() {
|
||||
return INSTANCE;
|
||||
}
|
||||
|
||||
@Override
|
||||
public MapCodec<? extends ConditionalItemModelProperty> type() {
|
||||
return CODEC;
|
||||
}
|
||||
}
|
@@ -17,9 +17,9 @@ import net.minecraft.client.model.geom.builders.MeshDefinition;
|
||||
import net.minecraft.client.renderer.LightTexture;
|
||||
import net.minecraft.client.renderer.MultiBufferSource;
|
||||
import net.minecraft.client.renderer.RenderType;
|
||||
import net.minecraft.client.renderer.texture.TextureAtlas;
|
||||
import net.minecraft.client.resources.model.Material;
|
||||
import net.minecraft.resources.ResourceLocation;
|
||||
import net.minecraft.world.inventory.InventoryMenu;
|
||||
import net.minecraft.world.item.component.DyedItemColor;
|
||||
|
||||
/**
|
||||
@@ -34,11 +34,11 @@ public class LecternPocketModel {
|
||||
public static final ResourceLocation TEXTURE_FRAME = ResourceLocation.fromNamespaceAndPath(ComputerCraftAPI.MOD_ID, "entity/pocket_computer_frame");
|
||||
public static final ResourceLocation TEXTURE_LIGHT = ResourceLocation.fromNamespaceAndPath(ComputerCraftAPI.MOD_ID, "entity/pocket_computer_light");
|
||||
|
||||
private static final Material MATERIAL_NORMAL = new Material(InventoryMenu.BLOCK_ATLAS, TEXTURE_NORMAL);
|
||||
private static final Material MATERIAL_ADVANCED = new Material(InventoryMenu.BLOCK_ATLAS, TEXTURE_ADVANCED);
|
||||
private static final Material MATERIAL_COLOUR = new Material(InventoryMenu.BLOCK_ATLAS, TEXTURE_COLOUR);
|
||||
private static final Material MATERIAL_FRAME = new Material(InventoryMenu.BLOCK_ATLAS, TEXTURE_FRAME);
|
||||
private static final Material MATERIAL_LIGHT = new Material(InventoryMenu.BLOCK_ATLAS, TEXTURE_LIGHT);
|
||||
private static final Material MATERIAL_NORMAL = new Material(TextureAtlas.LOCATION_BLOCKS, TEXTURE_NORMAL);
|
||||
private static final Material MATERIAL_ADVANCED = new Material(TextureAtlas.LOCATION_BLOCKS, TEXTURE_ADVANCED);
|
||||
private static final Material MATERIAL_COLOUR = new Material(TextureAtlas.LOCATION_BLOCKS, TEXTURE_COLOUR);
|
||||
private static final Material MATERIAL_FRAME = new Material(TextureAtlas.LOCATION_BLOCKS, TEXTURE_FRAME);
|
||||
private static final Material MATERIAL_LIGHT = new Material(TextureAtlas.LOCATION_BLOCKS, TEXTURE_LIGHT);
|
||||
|
||||
// The size of the terminal within the model.
|
||||
public static final float TERM_WIDTH = 12.0f / 32.0f;
|
||||
|
@@ -13,9 +13,9 @@ import net.minecraft.client.model.geom.ModelPart;
|
||||
import net.minecraft.client.model.geom.PartPose;
|
||||
import net.minecraft.client.model.geom.builders.CubeListBuilder;
|
||||
import net.minecraft.client.model.geom.builders.MeshDefinition;
|
||||
import net.minecraft.client.renderer.texture.TextureAtlas;
|
||||
import net.minecraft.client.resources.model.Material;
|
||||
import net.minecraft.resources.ResourceLocation;
|
||||
import net.minecraft.world.inventory.InventoryMenu;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@@ -29,7 +29,7 @@ import java.util.List;
|
||||
*/
|
||||
public class LecternPrintoutModel {
|
||||
public static final ResourceLocation TEXTURE = ResourceLocation.fromNamespaceAndPath(ComputerCraftAPI.MOD_ID, "entity/printout");
|
||||
public static final Material MATERIAL = new Material(InventoryMenu.BLOCK_ATLAS, TEXTURE);
|
||||
public static final Material MATERIAL = new Material(TextureAtlas.LOCATION_BLOCKS, TEXTURE);
|
||||
|
||||
private static final int TEXTURE_WIDTH = 32;
|
||||
private static final int TEXTURE_HEIGHT = 32;
|
||||
|
@@ -1,92 +0,0 @@
|
||||
// SPDX-FileCopyrightText: 2023 The CC: Tweaked Developers
|
||||
//
|
||||
// SPDX-License-Identifier: MPL-2.0
|
||||
|
||||
package dan200.computercraft.client.model.turtle;
|
||||
|
||||
import com.mojang.math.Transformation;
|
||||
import net.minecraft.client.renderer.block.model.BakedQuad;
|
||||
import net.minecraft.client.renderer.block.model.FaceBakery;
|
||||
import net.minecraft.client.resources.model.BakedModel;
|
||||
import net.minecraft.core.Direction;
|
||||
import org.joml.Matrix4f;
|
||||
import org.joml.Vector4f;
|
||||
import org.jspecify.annotations.Nullable;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Applies a {@link Transformation} (or rather a {@link Matrix4f}) to a list of {@link BakedQuad}s.
|
||||
* <p>
|
||||
* This does a little bit of magic compared with other system (i.e. Forge's {@code QuadTransformers}), as it needs to
|
||||
* handle flipping models upside down.
|
||||
* <p>
|
||||
* This is typically used with a {@link BakedModel} subclass - see the loader-specific projects.
|
||||
*/
|
||||
public class ModelTransformer {
|
||||
private static final int[] INVERSE_ORDER = new int[]{ 3, 2, 1, 0 };
|
||||
|
||||
private static final int STRIDE = FaceBakery.VERTEX_INT_SIZE;
|
||||
private static final int POS_OFFSET = 0;
|
||||
|
||||
protected final Matrix4f transformation;
|
||||
protected final boolean invert;
|
||||
private @Nullable TransformedQuads cache;
|
||||
|
||||
public ModelTransformer(Transformation transformation) {
|
||||
this.transformation = transformation.getMatrix();
|
||||
invert = transformation.getMatrix().determinant() < 0;
|
||||
}
|
||||
|
||||
public List<BakedQuad> transform(List<BakedQuad> quads) {
|
||||
if (quads.isEmpty()) return List.of();
|
||||
|
||||
// We do some basic caching here to avoid recomputing every frame. Most turtle models don't have culled faces,
|
||||
// so it's not worth being smarter here.
|
||||
var cache = this.cache;
|
||||
if (cache != null && quads.equals(cache.original())) return cache.transformed();
|
||||
|
||||
List<BakedQuad> transformed = new ArrayList<>(quads.size());
|
||||
for (var quad : quads) transformed.add(transformQuad(quad));
|
||||
this.cache = new TransformedQuads(quads, transformed);
|
||||
return transformed;
|
||||
}
|
||||
|
||||
private BakedQuad transformQuad(BakedQuad quad) {
|
||||
var inputData = quad.getVertices();
|
||||
var outputData = new int[inputData.length];
|
||||
for (var i = 0; i < 4; i++) {
|
||||
var inStart = STRIDE * i;
|
||||
// Reverse the order of the quads if we're inverting
|
||||
var outStart = getVertexOffset(i, invert);
|
||||
System.arraycopy(inputData, inStart, outputData, outStart, STRIDE);
|
||||
|
||||
// Apply the matrix to our position
|
||||
var inPosStart = inStart + POS_OFFSET;
|
||||
var outPosStart = outStart + POS_OFFSET;
|
||||
|
||||
var x = Float.intBitsToFloat(inputData[inPosStart]);
|
||||
var y = Float.intBitsToFloat(inputData[inPosStart + 1]);
|
||||
var z = Float.intBitsToFloat(inputData[inPosStart + 2]);
|
||||
|
||||
// Transform the position
|
||||
var pos = new Vector4f(x, y, z, 1);
|
||||
transformation.transformProject(pos);
|
||||
|
||||
outputData[outPosStart] = Float.floatToRawIntBits(pos.x());
|
||||
outputData[outPosStart + 1] = Float.floatToRawIntBits(pos.y());
|
||||
outputData[outPosStart + 2] = Float.floatToRawIntBits(pos.z());
|
||||
}
|
||||
|
||||
var direction = Direction.rotate(transformation, quad.getDirection());
|
||||
return new BakedQuad(outputData, quad.getTintIndex(), direction, quad.getSprite(), quad.isShade());
|
||||
}
|
||||
|
||||
public static int getVertexOffset(int vertex, boolean invert) {
|
||||
return (invert ? ModelTransformer.INVERSE_ORDER[vertex] : vertex) * ModelTransformer.STRIDE;
|
||||
}
|
||||
|
||||
private record TransformedQuads(List<BakedQuad> original, List<BakedQuad> transformed) {
|
||||
}
|
||||
}
|
@@ -1,146 +0,0 @@
|
||||
// Copyright Daniel Ratcliffe, 2011-2022. Do not distribute without permission.
|
||||
//
|
||||
// SPDX-License-Identifier: LicenseRef-CCPL
|
||||
|
||||
package dan200.computercraft.client.model.turtle;
|
||||
|
||||
import com.google.common.cache.CacheBuilder;
|
||||
import com.mojang.blaze3d.vertex.PoseStack;
|
||||
import com.mojang.math.Transformation;
|
||||
import dan200.computercraft.api.client.TransformedModel;
|
||||
import dan200.computercraft.api.turtle.ITurtleUpgrade;
|
||||
import dan200.computercraft.api.turtle.TurtleSide;
|
||||
import dan200.computercraft.api.upgrades.UpgradeData;
|
||||
import dan200.computercraft.client.platform.ClientPlatformHelper;
|
||||
import dan200.computercraft.client.turtle.TurtleUpgradeModellers;
|
||||
import dan200.computercraft.shared.turtle.TurtleOverlay;
|
||||
import dan200.computercraft.shared.turtle.items.TurtleItem;
|
||||
import dan200.computercraft.shared.util.DataComponentUtil;
|
||||
import dan200.computercraft.shared.util.Holiday;
|
||||
import net.minecraft.client.Minecraft;
|
||||
import net.minecraft.client.resources.model.BakedModel;
|
||||
import net.minecraft.client.resources.model.ModelManager;
|
||||
import net.minecraft.core.component.DataComponents;
|
||||
import net.minecraft.resources.ResourceLocation;
|
||||
import net.minecraft.world.item.ItemStack;
|
||||
import org.jspecify.annotations.Nullable;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.function.Function;
|
||||
|
||||
/**
|
||||
* Combines several individual models together to form a turtle.
|
||||
*
|
||||
* @param <T> The type of the resulting "baked model".
|
||||
*/
|
||||
public final class TurtleModelParts<T> {
|
||||
private static final Transformation identity, flip;
|
||||
|
||||
static {
|
||||
var stack = new PoseStack();
|
||||
stack.translate(0.5f, 0.5f, 0.5f);
|
||||
stack.scale(1, -1, 1);
|
||||
stack.translate(-0.5f, -0.5f, -0.5f);
|
||||
|
||||
identity = Transformation.identity();
|
||||
flip = new Transformation(stack.last().pose());
|
||||
}
|
||||
|
||||
private record Combination(
|
||||
boolean colour,
|
||||
@Nullable UpgradeData<ITurtleUpgrade> leftUpgrade,
|
||||
@Nullable UpgradeData<ITurtleUpgrade> rightUpgrade,
|
||||
@Nullable TurtleOverlay overlay,
|
||||
boolean christmas,
|
||||
boolean flip
|
||||
) {
|
||||
}
|
||||
|
||||
private final BakedModel familyModel;
|
||||
private final BakedModel colourModel;
|
||||
private final Function<TransformedModel, BakedModel> transformer;
|
||||
private final Function<Combination, T> buildModel;
|
||||
|
||||
/**
|
||||
* A cache of {@link TransformedModel} to the transformed {@link BakedModel}. This helps us pool the transformed
|
||||
* instances, reducing memory usage and hopefully ensuring their caches are hit more often!
|
||||
*/
|
||||
private final Map<TransformedModel, BakedModel> transformCache = CacheBuilder.newBuilder()
|
||||
.concurrencyLevel(1)
|
||||
.expireAfterAccess(30, TimeUnit.SECONDS)
|
||||
.<TransformedModel, BakedModel>build()
|
||||
.asMap();
|
||||
|
||||
/**
|
||||
* A cache of {@link Combination}s to the combined model.
|
||||
*/
|
||||
private final Map<Combination, T> modelCache = CacheBuilder.newBuilder()
|
||||
.concurrencyLevel(1)
|
||||
.expireAfterAccess(30, TimeUnit.SECONDS)
|
||||
.<Combination, T>build()
|
||||
.asMap();
|
||||
|
||||
public TurtleModelParts(BakedModel familyModel, BakedModel colourModel, ModelTransformer transformer, Function<List<BakedModel>, T> combineModel) {
|
||||
this.familyModel = familyModel;
|
||||
this.colourModel = colourModel;
|
||||
this.transformer = x -> transformer.transform(x.model(), x.matrix());
|
||||
buildModel = x -> combineModel.apply(buildModel(x));
|
||||
}
|
||||
|
||||
public T getModel(ItemStack stack) {
|
||||
var combination = getCombination(stack);
|
||||
return modelCache.computeIfAbsent(combination, buildModel);
|
||||
}
|
||||
|
||||
private Combination getCombination(ItemStack stack) {
|
||||
var christmas = Holiday.getCurrent() == Holiday.CHRISTMAS;
|
||||
var leftUpgrade = TurtleItem.getUpgradeWithData(stack, TurtleSide.LEFT);
|
||||
var rightUpgrade = TurtleItem.getUpgradeWithData(stack, TurtleSide.RIGHT);
|
||||
var overlay = TurtleItem.getOverlay(stack);
|
||||
var label = DataComponentUtil.getCustomName(stack);
|
||||
var flip = label != null && (label.equals("Dinnerbone") || label.equals("Grumm"));
|
||||
|
||||
return new Combination(stack.has(DataComponents.DYED_COLOR), leftUpgrade, rightUpgrade, overlay, christmas, flip);
|
||||
}
|
||||
|
||||
private List<BakedModel> buildModel(Combination combo) {
|
||||
var mc = Minecraft.getInstance();
|
||||
var modelManager = mc.getItemRenderer().getItemModelShaper().getModelManager();
|
||||
|
||||
var transformation = combo.flip ? flip : identity;
|
||||
var parts = new ArrayList<BakedModel>(4);
|
||||
parts.add(transform(combo.colour() ? colourModel : familyModel, transformation));
|
||||
|
||||
if (combo.overlay() != null) addPart(parts, modelManager, transformation, combo.overlay().model());
|
||||
|
||||
var showChristmas = TurtleOverlay.showElfOverlay(combo.overlay(), combo.christmas());
|
||||
if (showChristmas) addPart(parts, modelManager, transformation, TurtleOverlay.ELF_MODEL);
|
||||
|
||||
addUpgrade(parts, transformation, TurtleSide.LEFT, combo.leftUpgrade());
|
||||
addUpgrade(parts, transformation, TurtleSide.RIGHT, combo.rightUpgrade());
|
||||
|
||||
return parts;
|
||||
}
|
||||
|
||||
private void addPart(List<BakedModel> parts, ModelManager modelManager, Transformation transformation, ResourceLocation model) {
|
||||
parts.add(transform(ClientPlatformHelper.get().getModel(modelManager, model), transformation));
|
||||
}
|
||||
|
||||
private void addUpgrade(List<BakedModel> parts, Transformation transformation, TurtleSide side, @Nullable UpgradeData<ITurtleUpgrade> upgrade) {
|
||||
if (upgrade == null) return;
|
||||
var model = TurtleUpgradeModellers.getModel(upgrade.upgrade(), upgrade.data(), side);
|
||||
parts.add(transform(model.model(), transformation.compose(model.matrix())));
|
||||
}
|
||||
|
||||
private BakedModel transform(BakedModel model, Transformation transformation) {
|
||||
if (transformation.equals(Transformation.identity())) return model;
|
||||
return transformCache.computeIfAbsent(new TransformedModel(model, transformation), transformer);
|
||||
}
|
||||
|
||||
public interface ModelTransformer {
|
||||
BakedModel transform(BakedModel model, Transformation transformation);
|
||||
}
|
||||
}
|
@@ -22,11 +22,13 @@ import dan200.computercraft.shared.peripheral.speaker.SpeakerPosition;
|
||||
import net.minecraft.client.Minecraft;
|
||||
import net.minecraft.core.BlockPos;
|
||||
import net.minecraft.core.Holder;
|
||||
import net.minecraft.core.registries.Registries;
|
||||
import net.minecraft.network.chat.Component;
|
||||
import net.minecraft.resources.ResourceLocation;
|
||||
import net.minecraft.world.entity.player.Player;
|
||||
import net.minecraft.world.item.JukeboxSong;
|
||||
import net.minecraft.world.level.Level;
|
||||
import net.minecraft.world.level.block.LevelEvent;
|
||||
import org.jspecify.annotations.Nullable;
|
||||
|
||||
import java.util.UUID;
|
||||
@@ -62,10 +64,14 @@ public final class ClientNetworkContextImpl implements ClientNetworkContext {
|
||||
|
||||
@Override
|
||||
public void handlePlayRecord(BlockPos pos, @Nullable Holder<JukeboxSong> song) {
|
||||
var level = Minecraft.getInstance().level;
|
||||
if (level == null) return;
|
||||
|
||||
if (song == null) {
|
||||
Minecraft.getInstance().levelRenderer.stopJukeboxSongAndNotifyNearby(pos);
|
||||
level.levelEvent(LevelEvent.SOUND_STOP_JUKEBOX_SONG, pos, 0);
|
||||
} else {
|
||||
Minecraft.getInstance().levelRenderer.playJukeboxSong(song, pos);
|
||||
var id = level.registryAccess().lookupOrThrow(Registries.JUKEBOX_SONG).getIdOrThrow(song.value());
|
||||
level.levelEvent(LevelEvent.SOUND_PLAY_JUKEBOX_SONG, pos, id);
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -0,0 +1,47 @@
|
||||
// SPDX-FileCopyrightText: 2025 The CC: Tweaked Developers
|
||||
//
|
||||
// SPDX-License-Identifier: MPL-2.0
|
||||
|
||||
package dan200.computercraft.client.render;
|
||||
|
||||
import com.mojang.blaze3d.vertex.PoseStack;
|
||||
import com.mojang.blaze3d.vertex.VertexConsumer;
|
||||
import dan200.computercraft.client.ClientHooks;
|
||||
import net.minecraft.client.Camera;
|
||||
import net.minecraft.client.Minecraft;
|
||||
import net.minecraft.client.renderer.LevelRenderer;
|
||||
import net.minecraft.client.renderer.MultiBufferSource;
|
||||
import net.minecraft.client.renderer.RenderType;
|
||||
import net.minecraft.util.ARGB;
|
||||
import net.minecraft.util.CommonColors;
|
||||
import net.minecraft.world.phys.BlockHitResult;
|
||||
|
||||
/**
|
||||
* Utilities for rendering block outline.
|
||||
*
|
||||
* @see ClientHooks#drawHighlight(PoseStack, MultiBufferSource, Camera, BlockHitResult)
|
||||
*/
|
||||
public final class BlockOutlineRenderer {
|
||||
private BlockOutlineRenderer() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Render a block outline, handling both normal and high-contrast modes.
|
||||
*
|
||||
* @param bufferSource The buffer source.
|
||||
* @param renderer The function to render a highlight.
|
||||
* @see LevelRenderer#renderBlockOutline(Camera, MultiBufferSource.BufferSource, PoseStack, boolean)
|
||||
*/
|
||||
public static void render(MultiBufferSource bufferSource, Renderer renderer) {
|
||||
var highContrast = Minecraft.getInstance().options.highContrastBlockOutline().get();
|
||||
if (highContrast) renderer.render(bufferSource.getBuffer(RenderType.secondaryBlockOutline()), 0xff000000);
|
||||
|
||||
var colour = highContrast ? CommonColors.HIGH_CONTRAST_DIAMOND : ARGB.color(0x66, CommonColors.BLACK);
|
||||
renderer.render(bufferSource.getBuffer(RenderType.lines()), colour);
|
||||
}
|
||||
|
||||
@FunctionalInterface
|
||||
public interface Renderer {
|
||||
void render(VertexConsumer buffer, int colour);
|
||||
}
|
||||
}
|
@@ -11,8 +11,7 @@ import dan200.computercraft.shared.peripheral.modem.wired.CableShapes;
|
||||
import dan200.computercraft.shared.util.WorldUtil;
|
||||
import net.minecraft.client.Camera;
|
||||
import net.minecraft.client.renderer.MultiBufferSource;
|
||||
import net.minecraft.client.renderer.RenderType;
|
||||
import net.minecraft.util.Mth;
|
||||
import net.minecraft.client.renderer.ShapeRenderer;
|
||||
import net.minecraft.world.phys.BlockHitResult;
|
||||
|
||||
public final class CableHighlightRenderer {
|
||||
@@ -27,7 +26,6 @@ public final class CableHighlightRenderer {
|
||||
* @param camera The current camera.
|
||||
* @param hit The block hit result for the current player.
|
||||
* @return If we rendered a custom outline.
|
||||
* @see net.minecraft.client.renderer.LevelRenderer#renderHitOutline
|
||||
*/
|
||||
public static boolean drawHighlight(PoseStack transform, MultiBufferSource bufferSource, Camera camera, BlockHitResult hit) {
|
||||
var pos = hit.getBlockPos();
|
||||
@@ -49,27 +47,9 @@ public final class CableHighlightRenderer {
|
||||
var yOffset = pos.getY() - cameraPos.y();
|
||||
var zOffset = pos.getZ() - cameraPos.z();
|
||||
|
||||
var buffer = bufferSource.getBuffer(RenderType.lines());
|
||||
var matrix4f = transform.last().pose();
|
||||
// TODO: Can we just accesstransformer out LevelRenderer.renderShape?
|
||||
shape.forAllEdges((x1, y1, z1, x2, y2, z2) -> {
|
||||
var xDelta = (float) (x2 - x1);
|
||||
var yDelta = (float) (y2 - y1);
|
||||
var zDelta = (float) (z2 - z1);
|
||||
var len = Mth.sqrt(xDelta * xDelta + yDelta * yDelta + zDelta * zDelta);
|
||||
xDelta = xDelta / len;
|
||||
yDelta = yDelta / len;
|
||||
zDelta = zDelta / len;
|
||||
|
||||
buffer
|
||||
.addVertex(matrix4f, (float) (x1 + xOffset), (float) (y1 + yOffset), (float) (z1 + zOffset))
|
||||
.setColor(0, 0, 0, 0.4f)
|
||||
.setNormal(transform.last(), xDelta, yDelta, zDelta);
|
||||
buffer
|
||||
.addVertex(matrix4f, (float) (x2 + xOffset), (float) (y2 + yOffset), (float) (z2 + zOffset))
|
||||
.setColor(0, 0, 0, 0.4f)
|
||||
.setNormal(transform.last(), xDelta, yDelta, zDelta);
|
||||
});
|
||||
BlockOutlineRenderer.render(
|
||||
bufferSource, (buffer, colour) -> ShapeRenderer.renderShape(transform, buffer, shape, xOffset, yOffset, zOffset, colour)
|
||||
);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
@@ -22,7 +22,7 @@ import net.minecraft.client.renderer.blockentity.BlockEntityRenderDispatcher;
|
||||
import net.minecraft.client.renderer.blockentity.BlockEntityRenderer;
|
||||
import net.minecraft.client.renderer.blockentity.BlockEntityRendererProvider;
|
||||
import net.minecraft.client.renderer.blockentity.LecternRenderer;
|
||||
import net.minecraft.util.FastColor;
|
||||
import net.minecraft.util.ARGB;
|
||||
import net.minecraft.world.item.component.DyedItemColor;
|
||||
import net.minecraft.world.level.block.LecternBlock;
|
||||
import net.minecraft.world.phys.Vec3;
|
||||
@@ -71,7 +71,7 @@ public class CustomLecternRenderer implements BlockEntityRenderer<CustomLecternB
|
||||
|
||||
pocketModel.render(
|
||||
poseStack, buffer, packedLight, packedOverlay, pocket.getFamily(), DyedItemColor.getOrDefault(item, -1),
|
||||
FastColor.ARGB32.opaque(computer == null || computer.getLightState() == -1 ? Colour.BLACK.getHex() : computer.getLightState())
|
||||
ARGB.opaque(computer == null || computer.getLightState() == -1 ? Colour.BLACK.getHex() : computer.getLightState())
|
||||
);
|
||||
|
||||
// Jiggle the terminal about a bit, so (0, 0) is in the top left of the model's terminal hole.
|
||||
@@ -81,7 +81,7 @@ public class CustomLecternRenderer implements BlockEntityRenderer<CustomLecternB
|
||||
|
||||
// Either render the terminal or a black screen, depending on how close we are.
|
||||
var terminal = computer == null ? null : computer.getTerminal();
|
||||
var quadEmitter = FixedWidthFontRenderer.toVertexConsumer(poseStack, buffer.getBuffer(RenderTypes.TERMINAL));
|
||||
var quadEmitter = FixedWidthFontRenderer.toVertexConsumer(poseStack, buffer.getBuffer(FixedWidthFontRenderer.TERMINAL_TEXT));
|
||||
if (terminal != null && Vec3.atCenterOf(lectern.getBlockPos()).closerThan(berDispatcher.camera.getPosition(), POCKET_TERMINAL_RENDER_DISTANCE)) {
|
||||
renderPocketTerminal(poseStack, quadEmitter, terminal);
|
||||
} else {
|
||||
|
@@ -0,0 +1,41 @@
|
||||
// SPDX-FileCopyrightText: 2025 The CC: Tweaked Developers
|
||||
//
|
||||
// SPDX-License-Identifier: MPL-2.0
|
||||
|
||||
package dan200.computercraft.client.render;
|
||||
|
||||
import com.mojang.blaze3d.vertex.PoseStack;
|
||||
import dan200.computercraft.shared.ModRegistry;
|
||||
import dan200.computercraft.shared.media.items.PrintoutData;
|
||||
import dan200.computercraft.shared.media.items.PrintoutItem;
|
||||
import net.minecraft.client.renderer.MultiBufferSource;
|
||||
import net.minecraft.client.renderer.entity.state.ItemFrameRenderState;
|
||||
import net.minecraft.world.entity.decoration.ItemFrame;
|
||||
import net.minecraft.world.item.ItemStack;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
/**
|
||||
* Additional render state attached to a {@link ItemFrameRenderState}.
|
||||
*
|
||||
* @see dan200.computercraft.client.ClientHooks#onRenderItemFrame(PoseStack, MultiBufferSource, ItemFrameRenderState, ExtendedItemFrameRenderState, int)
|
||||
*/
|
||||
public class ExtendedItemFrameRenderState {
|
||||
public @Nullable PrintoutData printoutData;
|
||||
public boolean isBook;
|
||||
|
||||
/**
|
||||
* Set up the render state from the {@link ItemFrame}'s {@link ItemStack}.
|
||||
*
|
||||
* @param stack The item frame's item.
|
||||
*/
|
||||
public void setup(ItemStack stack) {
|
||||
if (stack.getItem() instanceof PrintoutItem) {
|
||||
printoutData = PrintoutData.getOrEmpty(stack);
|
||||
isBook = stack.getItem() == ModRegistry.Items.PRINTED_BOOK.get();
|
||||
} else {
|
||||
printoutData = null;
|
||||
isBook = false;
|
||||
}
|
||||
}
|
||||
}
|
@@ -6,14 +6,12 @@ package dan200.computercraft.client.render;
|
||||
|
||||
import com.mojang.blaze3d.vertex.PoseStack;
|
||||
import com.mojang.blaze3d.vertex.VertexConsumer;
|
||||
import dan200.computercraft.client.model.turtle.ModelTransformer;
|
||||
import dan200.computercraft.client.platform.ClientPlatformHelper;
|
||||
import net.minecraft.client.renderer.MultiBufferSource;
|
||||
import net.minecraft.client.renderer.block.model.BakedQuad;
|
||||
import net.minecraft.client.renderer.entity.ItemRenderer;
|
||||
import net.minecraft.client.resources.model.BakedModel;
|
||||
import net.minecraft.world.item.ItemStack;
|
||||
import org.joml.Vector4f;
|
||||
import net.minecraft.util.ARGB;
|
||||
import org.jspecify.annotations.Nullable;
|
||||
|
||||
import java.util.List;
|
||||
@@ -29,8 +27,7 @@ public final class ModelRenderer {
|
||||
* Render a list of {@linkplain BakedQuad quads} to a buffer.
|
||||
* <p>
|
||||
* This is not intended to be used directly, but instead by {@link ClientPlatformHelper#renderBakedModel(PoseStack, MultiBufferSource, BakedModel, int, int, int[])}. The
|
||||
* implementation here is pretty similar to {@link ItemRenderer#renderQuadList(PoseStack, VertexConsumer, List, ItemStack, int, int)},
|
||||
* but supports inverted quads (i.e. those with a negative scale).
|
||||
* implementation here is identical to {@link ItemRenderer#renderQuadList(PoseStack, VertexConsumer, List, int[], int, int)}.
|
||||
*
|
||||
* @param transform The current matrix transformation to apply.
|
||||
* @param buffer The buffer to draw to.
|
||||
@@ -41,58 +38,21 @@ public final class ModelRenderer {
|
||||
*/
|
||||
public static void renderQuads(PoseStack transform, VertexConsumer buffer, List<BakedQuad> quads, int lightmapCoord, int overlayLight, int @Nullable [] tints) {
|
||||
var matrix = transform.last();
|
||||
var inverted = matrix.pose().determinant() < 0;
|
||||
|
||||
for (var bakedquad : quads) {
|
||||
var tint = -1;
|
||||
float r = 1.0f, g = 1.0f, b = 1.0f, a = 1.0f;
|
||||
if (tints != null && bakedquad.isTinted()) {
|
||||
var idx = bakedquad.getTintIndex();
|
||||
if (idx >= 0 && idx < tints.length) tint = tints[bakedquad.getTintIndex()];
|
||||
if (idx >= 0 && idx < tints.length) {
|
||||
var tint = tints[bakedquad.getTintIndex()];
|
||||
r = ARGB.red(tint) / 255.0f;
|
||||
g = ARGB.green(tint) / 255.0f;
|
||||
b = ARGB.blue(tint) / 255.0f;
|
||||
a = ARGB.alpha(tint) / 255.0f;
|
||||
}
|
||||
}
|
||||
|
||||
putBulkQuad(buffer, matrix, bakedquad, tint, lightmapCoord, overlayLight, inverted);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A version of {@link VertexConsumer#putBulkData(PoseStack.Pose, BakedQuad, float, float, float, float, int, int)} which
|
||||
* will reverse vertex order when the matrix is inverted.
|
||||
*
|
||||
* @param buffer The buffer to draw to.
|
||||
* @param pose The current matrix stack.
|
||||
* @param quad The quad to draw.
|
||||
* @param colour The tint for this quad.
|
||||
* @param lightmapCoord The lightmap coordinate
|
||||
* @param overlayLight The overlay light.
|
||||
* @param invert Whether to reverse the order of this quad.
|
||||
*/
|
||||
private static void putBulkQuad(VertexConsumer buffer, PoseStack.Pose pose, BakedQuad quad, int colour, int lightmapCoord, int overlayLight, boolean invert) {
|
||||
var matrix = pose.pose();
|
||||
// It's a little dubious to transform using this matrix rather than the normal matrix. This mirrors the logic in
|
||||
// Direction.rotate (so not out of nowhere!), but is a little suspicious.
|
||||
var dirNormal = quad.getDirection().getNormal();
|
||||
var vector = new Vector4f();
|
||||
|
||||
matrix.transform(dirNormal.getX(), dirNormal.getY(), dirNormal.getZ(), 0.0f, vector).normalize();
|
||||
float normalX = vector.x(), normalY = vector.y(), normalZ = vector.z();
|
||||
|
||||
var vertices = quad.getVertices();
|
||||
for (var vertex = 0; vertex < 4; vertex++) {
|
||||
var i = ModelTransformer.getVertexOffset(vertex, invert);
|
||||
|
||||
var x = Float.intBitsToFloat(vertices[i]);
|
||||
var y = Float.intBitsToFloat(vertices[i + 1]);
|
||||
var z = Float.intBitsToFloat(vertices[i + 2]);
|
||||
|
||||
matrix.transform(x, y, z, 1, vector);
|
||||
|
||||
var u = Float.intBitsToFloat(vertices[i + 4]);
|
||||
var v = Float.intBitsToFloat(vertices[i + 5]);
|
||||
buffer.addVertex(
|
||||
vector.x(), vector.y(), vector.z(),
|
||||
colour, u, v, overlayLight, lightmapCoord,
|
||||
normalX, normalY, normalZ
|
||||
);
|
||||
buffer.putBulkData(matrix, bakedquad, r, g, b, a, lightmapCoord, overlayLight);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -13,8 +13,10 @@ import dan200.computercraft.core.util.Colour;
|
||||
import dan200.computercraft.shared.computer.core.ComputerFamily;
|
||||
import dan200.computercraft.shared.config.Config;
|
||||
import dan200.computercraft.shared.pocket.items.PocketComputerItem;
|
||||
import net.minecraft.client.renderer.LightTexture;
|
||||
import net.minecraft.client.renderer.MultiBufferSource;
|
||||
import net.minecraft.util.FastColor;
|
||||
import net.minecraft.client.renderer.RenderType;
|
||||
import net.minecraft.util.ARGB;
|
||||
import net.minecraft.world.item.ItemStack;
|
||||
import net.minecraft.world.item.component.DyedItemColor;
|
||||
import org.joml.Matrix4f;
|
||||
@@ -72,7 +74,7 @@ public final class PocketItemRenderer extends ItemMapLikeRenderer {
|
||||
var lightColour = computer == null || computer.getLightState() == -1 ? Colour.BLACK.getHex() : computer.getLightState();
|
||||
renderLight(transform, bufferSource, lightColour, width, height);
|
||||
|
||||
var quadEmitter = FixedWidthFontRenderer.toVertexConsumer(transform, bufferSource.getBuffer(RenderTypes.TERMINAL));
|
||||
var quadEmitter = FixedWidthFontRenderer.toVertexConsumer(transform, bufferSource.getBuffer(FixedWidthFontRenderer.TERMINAL_TEXT));
|
||||
if (terminal == null) {
|
||||
FixedWidthFontRenderer.drawEmptyTerminal(quadEmitter, 0, 0, width, height);
|
||||
} else {
|
||||
@@ -89,16 +91,16 @@ public final class PocketItemRenderer extends ItemMapLikeRenderer {
|
||||
var g = (colour >>> 8) & 0xFF;
|
||||
var b = colour & 0xFF;
|
||||
|
||||
var spriteRenderer = new SpriteRenderer(transform, render.getBuffer(RenderTypes.GUI_SPRITES), 0, light, r, g, b);
|
||||
var spriteRenderer = new SpriteRenderer(transform, render.getBuffer(RenderType.text(GuiSprites.TEXTURE)), 0, light, r, g, b);
|
||||
ComputerBorderRenderer.render(spriteRenderer, texture, 0, 0, width, height, true);
|
||||
}
|
||||
|
||||
private static void renderLight(PoseStack transform, MultiBufferSource render, int colour, int width, int height) {
|
||||
var buffer = render.getBuffer(RenderTypes.TERMINAL);
|
||||
var buffer = render.getBuffer(FixedWidthFontRenderer.TERMINAL_TEXT);
|
||||
FixedWidthFontRenderer.drawQuad(
|
||||
FixedWidthFontRenderer.toVertexConsumer(transform, buffer),
|
||||
width - LIGHT_HEIGHT * 2, height + BORDER / 2.0f, 0.001f, LIGHT_HEIGHT * 2, LIGHT_HEIGHT,
|
||||
FastColor.ARGB32.opaque(colour), RenderTypes.FULL_BRIGHT_LIGHTMAP
|
||||
ARGB.opaque(colour), LightTexture.FULL_BRIGHT
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@@ -8,10 +8,8 @@ import com.mojang.blaze3d.vertex.PoseStack;
|
||||
import com.mojang.math.Axis;
|
||||
import dan200.computercraft.shared.ModRegistry;
|
||||
import dan200.computercraft.shared.media.items.PrintoutData;
|
||||
import dan200.computercraft.shared.media.items.PrintoutItem;
|
||||
import net.minecraft.client.renderer.MultiBufferSource;
|
||||
import net.minecraft.world.entity.EntityType;
|
||||
import net.minecraft.world.entity.decoration.ItemFrame;
|
||||
import net.minecraft.client.renderer.entity.state.ItemFrameRenderState;
|
||||
import net.minecraft.world.item.ItemStack;
|
||||
|
||||
import static dan200.computercraft.client.render.PrintoutRenderer.*;
|
||||
@@ -35,25 +33,22 @@ public final class PrintoutItemRenderer extends ItemMapLikeRenderer {
|
||||
transform.scale(0.42f, 0.42f, -0.42f);
|
||||
transform.translate(-0.5f, -0.48f, 0.0f);
|
||||
|
||||
drawPrintout(transform, render, stack, light);
|
||||
drawPrintout(transform, render, PrintoutData.getOrEmpty(stack), stack.getItem() == ModRegistry.Items.PRINTED_BOOK.get(), light);
|
||||
}
|
||||
|
||||
public static void onRenderInFrame(PoseStack transform, MultiBufferSource render, ItemFrame frame, ItemStack stack, int packedLight) {
|
||||
public static void onRenderInFrame(PoseStack transform, MultiBufferSource render, ItemFrameRenderState frame, PrintoutData data, boolean isBook, int packedLight) {
|
||||
// Move a little bit forward to ensure we're not clipping with the frame
|
||||
transform.translate(0.0f, 0.0f, -0.001f);
|
||||
transform.mulPose(Axis.ZP.rotationDegrees(180f));
|
||||
transform.scale(0.95f, 0.95f, -0.95f);
|
||||
transform.translate(-0.5f, -0.5f, 0.0f);
|
||||
|
||||
var light = frame.getType() == EntityType.GLOW_ITEM_FRAME ? 0xf000d2 : packedLight; // See getLightVal.
|
||||
drawPrintout(transform, render, stack, light);
|
||||
var light = frame.isGlowFrame ? 0xf000d2 : packedLight; // See getLightCoords.
|
||||
drawPrintout(transform, render, data, isBook, light);
|
||||
}
|
||||
|
||||
private static void drawPrintout(PoseStack transform, MultiBufferSource render, ItemStack stack, int light) {
|
||||
var pageData = stack.getOrDefault(ModRegistry.DataComponents.PRINTOUT.get(), PrintoutData.EMPTY);
|
||||
|
||||
private static void drawPrintout(PoseStack transform, MultiBufferSource render, PrintoutData pageData, boolean book, int light) {
|
||||
var pages = pageData.pages();
|
||||
var book = ((PrintoutItem) stack.getItem()).getType() == PrintoutItem.Type.BOOK;
|
||||
|
||||
double width = LINE_LENGTH * FONT_WIDTH + X_TEXT_MARGIN * 2;
|
||||
double height = LINES_PER_PAGE * FONT_HEIGHT + Y_TEXT_MARGIN * 2;
|
||||
|
@@ -11,6 +11,8 @@ import dan200.computercraft.core.terminal.Palette;
|
||||
import dan200.computercraft.core.terminal.TextBuffer;
|
||||
import dan200.computercraft.shared.media.items.PrintoutData;
|
||||
import net.minecraft.client.renderer.MultiBufferSource;
|
||||
import net.minecraft.client.renderer.RenderType;
|
||||
import net.minecraft.resources.ResourceLocation;
|
||||
import org.joml.Matrix4f;
|
||||
|
||||
import java.util.List;
|
||||
@@ -23,6 +25,12 @@ import static dan200.computercraft.shared.media.items.PrintoutData.LINES_PER_PAG
|
||||
* {@linkplain PrintoutItemRenderer in-hand/item frame printouts}.
|
||||
*/
|
||||
public final class PrintoutRenderer {
|
||||
/**
|
||||
* Printout's background texture. {@link RenderType#text(ResourceLocation)} is a <em>little</em> questionable, but
|
||||
* it is what maps use, so should behave the same as vanilla in both item frames and in-hand.
|
||||
*/
|
||||
private static final RenderType BACKGROUND = RenderType.text(ResourceLocation.fromNamespaceAndPath("computercraft", "textures/gui/printout.png"));
|
||||
|
||||
private static final float BG_SIZE = 256.0f;
|
||||
|
||||
/**
|
||||
@@ -58,11 +66,14 @@ public final class PrintoutRenderer {
|
||||
private static final int COVER_Y = Y_SIZE;
|
||||
private static final int COVER_X = X_SIZE + 4 * X_FOLD_SIZE;
|
||||
|
||||
private static final float BOOK_Z_OFFSET = -0.1f;
|
||||
private static final float PAGE_Z_OFFSET = -0.01f;
|
||||
|
||||
private PrintoutRenderer() {
|
||||
}
|
||||
|
||||
public static void drawText(PoseStack transform, MultiBufferSource bufferSource, int x, int y, int start, int light, TextBuffer[] text, TextBuffer[] colours) {
|
||||
var buffer = bufferSource.getBuffer(RenderTypes.PRINTOUT_TEXT);
|
||||
var buffer = bufferSource.getBuffer(FixedWidthFontRenderer.TERMINAL_TEXT);
|
||||
var emitter = FixedWidthFontRenderer.toVertexConsumer(transform, buffer);
|
||||
for (var line = 0; line < LINES_PER_PAGE && line < text.length; line++) {
|
||||
FixedWidthFontRenderer.drawString(emitter,
|
||||
@@ -73,7 +84,7 @@ public final class PrintoutRenderer {
|
||||
}
|
||||
|
||||
public static void drawText(PoseStack transform, MultiBufferSource bufferSource, int x, int y, int start, int light, List<PrintoutData.Line> lines) {
|
||||
var buffer = bufferSource.getBuffer(RenderTypes.PRINTOUT_TEXT);
|
||||
var buffer = bufferSource.getBuffer(FixedWidthFontRenderer.TERMINAL_TEXT);
|
||||
var emitter = FixedWidthFontRenderer.toVertexConsumer(transform, buffer);
|
||||
for (var line = 0; line < LINES_PER_PAGE && line < lines.size(); line++) {
|
||||
var lineContents = lines.get(start + line);
|
||||
@@ -90,7 +101,7 @@ public final class PrintoutRenderer {
|
||||
var leftPages = page;
|
||||
var rightPages = pages - page - 1;
|
||||
|
||||
var buffer = bufferSource.getBuffer(RenderTypes.PRINTOUT_BACKGROUND);
|
||||
var buffer = bufferSource.getBuffer(BACKGROUND);
|
||||
|
||||
if (isBook) {
|
||||
// Border
|
||||
@@ -99,12 +110,12 @@ public final class PrintoutRenderer {
|
||||
var right = x + X_SIZE + offset - 4;
|
||||
|
||||
// Left and right border
|
||||
drawTexture(matrix, buffer, left - 4, y - 8, z - 0.02f, COVER_X, 0, COVER_SIZE, Y_SIZE + COVER_SIZE * 2, light);
|
||||
drawTexture(matrix, buffer, right, y - 8, z - 0.02f, COVER_X + COVER_SIZE, 0, COVER_SIZE, Y_SIZE + COVER_SIZE * 2, light);
|
||||
drawTexture(matrix, buffer, left - 4, y - 8, z + BOOK_Z_OFFSET, COVER_X, 0, COVER_SIZE, Y_SIZE + COVER_SIZE * 2, light);
|
||||
drawTexture(matrix, buffer, right, y - 8, z + BOOK_Z_OFFSET, COVER_X + COVER_SIZE, 0, COVER_SIZE, Y_SIZE + COVER_SIZE * 2, light);
|
||||
|
||||
// Draw centre panel (just stretched texture, sorry).
|
||||
drawTexture(matrix, buffer,
|
||||
x - offset, y, z - 0.02f, X_SIZE + offset * 2, Y_SIZE,
|
||||
x - offset, y, z + BOOK_Z_OFFSET, X_SIZE + offset * 2, Y_SIZE,
|
||||
COVER_X + COVER_SIZE / 2.0f, COVER_SIZE, COVER_SIZE, Y_SIZE,
|
||||
light
|
||||
);
|
||||
@@ -112,20 +123,20 @@ public final class PrintoutRenderer {
|
||||
var borderX = left;
|
||||
while (borderX < right) {
|
||||
double thisWidth = Math.min(right - borderX, X_SIZE);
|
||||
drawTexture(matrix, buffer, borderX, y - 8, z - 0.02f, 0, COVER_Y, (float) thisWidth, COVER_SIZE, light);
|
||||
drawTexture(matrix, buffer, borderX, y + Y_SIZE - 4, z - 0.02f, 0, COVER_Y + COVER_SIZE, (float) thisWidth, COVER_SIZE, light);
|
||||
drawTexture(matrix, buffer, borderX, y - 8, z + BOOK_Z_OFFSET, 0, COVER_Y, (float) thisWidth, COVER_SIZE, light);
|
||||
drawTexture(matrix, buffer, borderX, y + Y_SIZE - 4, z + BOOK_Z_OFFSET, 0, COVER_Y + COVER_SIZE, (float) thisWidth, COVER_SIZE, light);
|
||||
borderX = (float) (borderX + thisWidth);
|
||||
}
|
||||
}
|
||||
|
||||
// Current page background: Z-offset is interleaved between the "zeroth" left/right page and the first
|
||||
// left/right page, so that the "bold" border can be drawn over the edge where appropriate.
|
||||
drawTexture(matrix, buffer, x, y, z - 1e-3f * 0.5f, X_FOLD_SIZE * 2, 0, X_SIZE, Y_SIZE, light);
|
||||
drawTexture(matrix, buffer, x, y, z + PAGE_Z_OFFSET, X_FOLD_SIZE * 2, 0, X_SIZE, Y_SIZE, light);
|
||||
|
||||
// Left pages
|
||||
for (var n = 0; n <= leftPages; n++) {
|
||||
drawTexture(matrix, buffer,
|
||||
x - offsetAt(n), y, z - 1e-3f * n,
|
||||
x - offsetAt(n), y, z + PAGE_Z_OFFSET * (n + 1),
|
||||
// Use the left "bold" fold for the outermost page
|
||||
n == leftPages ? 0 : X_FOLD_SIZE, 0,
|
||||
X_FOLD_SIZE, Y_SIZE, light
|
||||
@@ -135,7 +146,7 @@ public final class PrintoutRenderer {
|
||||
// Right pages
|
||||
for (var n = 0; n <= rightPages; n++) {
|
||||
drawTexture(matrix, buffer,
|
||||
x + (X_SIZE - X_FOLD_SIZE) + offsetAt(n), y, z - 1e-3f * n,
|
||||
x + (X_SIZE - X_FOLD_SIZE) + offsetAt(n), y, z + PAGE_Z_OFFSET * (n + 1),
|
||||
// Two folds, then the main page. Use the right "bold" fold for the outermost page.
|
||||
X_FOLD_SIZE * 2 + X_SIZE + (n == rightPages ? X_FOLD_SIZE : 0), 0,
|
||||
X_FOLD_SIZE, Y_SIZE, light
|
||||
|
@@ -1,102 +0,0 @@
|
||||
// SPDX-FileCopyrightText: 2021 The CC: Tweaked Developers
|
||||
//
|
||||
// SPDX-License-Identifier: MPL-2.0
|
||||
|
||||
package dan200.computercraft.client.render;
|
||||
|
||||
import com.mojang.blaze3d.vertex.DefaultVertexFormat;
|
||||
import com.mojang.blaze3d.vertex.VertexFormat;
|
||||
import dan200.computercraft.api.ComputerCraftAPI;
|
||||
import dan200.computercraft.client.gui.GuiSprites;
|
||||
import dan200.computercraft.client.render.monitor.MonitorTextureBufferShader;
|
||||
import dan200.computercraft.client.render.text.FixedWidthFontRenderer;
|
||||
import net.minecraft.client.renderer.GameRenderer;
|
||||
import net.minecraft.client.renderer.RenderStateShard;
|
||||
import net.minecraft.client.renderer.RenderType;
|
||||
import net.minecraft.client.renderer.ShaderInstance;
|
||||
import net.minecraft.resources.ResourceLocation;
|
||||
import net.minecraft.server.packs.resources.ResourceProvider;
|
||||
import org.jspecify.annotations.Nullable;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Objects;
|
||||
import java.util.function.BiConsumer;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
/**
|
||||
* Shared {@link RenderType}s used throughout the mod.
|
||||
*/
|
||||
public class RenderTypes {
|
||||
public static final int FULL_BRIGHT_LIGHTMAP = (0xF << 4) | (0xF << 20);
|
||||
|
||||
private static @Nullable MonitorTextureBufferShader monitorTboShader;
|
||||
|
||||
/**
|
||||
* Renders a fullbright terminal.
|
||||
*/
|
||||
public static final RenderType TERMINAL = RenderType.text(FixedWidthFontRenderer.FONT);
|
||||
|
||||
/**
|
||||
* Renders a monitor with the TBO shader.
|
||||
*
|
||||
* @see MonitorTextureBufferShader
|
||||
*/
|
||||
public static final RenderType MONITOR_TBO = Types.MONITOR_TBO;
|
||||
|
||||
/**
|
||||
* A variant of {@link #TERMINAL} which uses the lightmap rather than rendering fullbright.
|
||||
*/
|
||||
public static final RenderType PRINTOUT_TEXT = RenderType.text(FixedWidthFontRenderer.FONT);
|
||||
|
||||
/**
|
||||
* Printout's background texture. {@link RenderType#text(ResourceLocation)} is a <em>little</em> questionable, but
|
||||
* it is what maps use, so should behave the same as vanilla in both item frames and in-hand.
|
||||
*/
|
||||
public static final RenderType PRINTOUT_BACKGROUND = RenderType.text(ResourceLocation.fromNamespaceAndPath("computercraft", "textures/gui/printout.png"));
|
||||
|
||||
/**
|
||||
* Render type for {@linkplain GuiSprites GUI sprites}.
|
||||
*/
|
||||
public static final RenderType GUI_SPRITES = RenderType.text(GuiSprites.TEXTURE);
|
||||
|
||||
public static MonitorTextureBufferShader getMonitorTextureBufferShader() {
|
||||
if (monitorTboShader == null) throw new NullPointerException("MonitorTboShader has not been registered");
|
||||
return monitorTboShader;
|
||||
}
|
||||
|
||||
public static ShaderInstance getTerminalShader() {
|
||||
return Objects.requireNonNull(GameRenderer.getRendertypeTextShader(), "Text shader has not been registered");
|
||||
}
|
||||
|
||||
public static void registerShaders(ResourceProvider resources, BiConsumer<ShaderInstance, Consumer<ShaderInstance>> load) throws IOException {
|
||||
load.accept(
|
||||
new MonitorTextureBufferShader(
|
||||
resources,
|
||||
ComputerCraftAPI.MOD_ID + "/monitor_tbo",
|
||||
MONITOR_TBO.format()
|
||||
),
|
||||
x -> monitorTboShader = (MonitorTextureBufferShader) x
|
||||
);
|
||||
}
|
||||
|
||||
private static final class Types extends RenderType {
|
||||
private static final RenderStateShard.TextureStateShard TERM_FONT_TEXTURE = new TextureStateShard(
|
||||
FixedWidthFontRenderer.FONT,
|
||||
false, false // blur, minimap
|
||||
);
|
||||
|
||||
static final RenderType MONITOR_TBO = RenderType.create(
|
||||
"monitor_tbo", DefaultVertexFormat.POSITION_TEX, VertexFormat.Mode.TRIANGLE_STRIP, 128,
|
||||
false, false, // useDelegate, needsSorting
|
||||
RenderType.CompositeState.builder()
|
||||
.setTextureState(TERM_FONT_TEXTURE)
|
||||
.setShaderState(new ShaderStateShard(RenderTypes::getMonitorTextureBufferShader))
|
||||
.createCompositeState(false)
|
||||
);
|
||||
|
||||
@SuppressWarnings("UnusedMethod")
|
||||
private Types(String name, VertexFormat format, VertexFormat.Mode mode, int buffer, boolean crumbling, boolean sort, Runnable setup, Runnable teardown) {
|
||||
super(name, format, mode, buffer, crumbling, sort, setup, teardown);
|
||||
}
|
||||
}
|
||||
}
|
@@ -5,11 +5,15 @@
|
||||
package dan200.computercraft.client.render;
|
||||
|
||||
import com.mojang.blaze3d.vertex.VertexConsumer;
|
||||
import dan200.computercraft.client.gui.GuiSprites;
|
||||
import net.minecraft.client.gui.GuiGraphics;
|
||||
import net.minecraft.client.renderer.LightTexture;
|
||||
import net.minecraft.client.renderer.RenderType;
|
||||
import net.minecraft.client.renderer.texture.TextureAtlasSprite;
|
||||
import org.joml.Matrix4f;
|
||||
|
||||
import java.util.function.Consumer;
|
||||
|
||||
/**
|
||||
* A {@link GuiGraphics}-equivalent which is suitable for both rendering in to a GUI and in-world (as part of an entity
|
||||
* renderer).
|
||||
@@ -34,11 +38,11 @@ public class SpriteRenderer {
|
||||
this.b = b;
|
||||
}
|
||||
|
||||
public static SpriteRenderer createForGui(GuiGraphics graphics, RenderType renderType) {
|
||||
return new SpriteRenderer(
|
||||
graphics.pose().last().pose(), graphics.bufferSource().getBuffer(renderType),
|
||||
0, RenderTypes.FULL_BRIGHT_LIGHTMAP, 255, 255, 255
|
||||
);
|
||||
public static void inGui(GuiGraphics graphics, Consumer<SpriteRenderer> renderer) {
|
||||
graphics.drawSpecial(bufferSource -> renderer.accept(new SpriteRenderer(
|
||||
graphics.pose().last().pose(), bufferSource.getBuffer(RenderType.guiTextured(GuiSprites.TEXTURE)),
|
||||
0, LightTexture.FULL_BRIGHT, 255, 255, 255
|
||||
)));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -68,7 +72,7 @@ public class SpriteRenderer {
|
||||
* @param textureWidth The width of the whole texture.
|
||||
*/
|
||||
public void blitHorizontalSliced(TextureAtlasSprite sprite, int x, int y, int width, int height, int leftBorder, int rightBorder, int textureWidth) {
|
||||
// TODO(1.20.2)/TODO(1.21.0): Drive this from mcmeta files, like vanilla does.
|
||||
// TODO(1.21.4): Drive this from mcmeta files, like vanilla does.
|
||||
if (width < leftBorder + rightBorder) throw new IllegalArgumentException("width is less than two borders");
|
||||
|
||||
var centerStart = SpriteRenderer.u(sprite, leftBorder, textureWidth);
|
||||
@@ -93,7 +97,7 @@ public class SpriteRenderer {
|
||||
* @param textureHeight The height of the whole texture.
|
||||
*/
|
||||
public void blitVerticalSliced(TextureAtlasSprite sprite, int x, int y, int width, int height, int topBorder, int bottomBorder, int textureHeight) {
|
||||
// TODO(1.20.2)/TODO(1.21.0): Drive this from mcmeta files, like vanilla does.
|
||||
// TODO(1.21.4): Drive this from mcmeta files, like vanilla does.
|
||||
if (width < topBorder + bottomBorder) throw new IllegalArgumentException("height is less than two borders");
|
||||
|
||||
var centerStart = SpriteRenderer.v(sprite, topBorder, textureHeight);
|
||||
|
@@ -6,11 +6,12 @@ package dan200.computercraft.client.render;
|
||||
|
||||
import com.mojang.blaze3d.vertex.PoseStack;
|
||||
import com.mojang.math.Axis;
|
||||
import com.mojang.math.Transformation;
|
||||
import dan200.computercraft.api.ComputerCraftAPI;
|
||||
import dan200.computercraft.api.client.TransformedModel;
|
||||
import dan200.computercraft.api.turtle.TurtleSide;
|
||||
import dan200.computercraft.client.platform.ClientPlatformHelper;
|
||||
import dan200.computercraft.client.turtle.TurtleUpgradeModellers;
|
||||
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;
|
||||
@@ -22,13 +23,17 @@ import net.minecraft.client.renderer.blockentity.BlockEntityRenderer;
|
||||
import net.minecraft.client.renderer.blockentity.BlockEntityRendererProvider;
|
||||
import net.minecraft.client.resources.model.BakedModel;
|
||||
import net.minecraft.resources.ResourceLocation;
|
||||
import net.minecraft.util.ARGB;
|
||||
import net.minecraft.util.CommonColors;
|
||||
import net.minecraft.util.FastColor;
|
||||
import net.minecraft.util.Mth;
|
||||
import net.minecraft.world.item.ItemDisplayContext;
|
||||
import net.minecraft.world.phys.BlockHitResult;
|
||||
import net.minecraft.world.phys.HitResult;
|
||||
import org.jspecify.annotations.Nullable;
|
||||
|
||||
public class TurtleBlockEntityRenderer implements BlockEntityRenderer<TurtleBlockEntity> {
|
||||
public static final ResourceLocation NORMAL_TURTLE_MODEL = ResourceLocation.fromNamespaceAndPath(ComputerCraftAPI.MOD_ID, "block/turtle_normal");
|
||||
public static final ResourceLocation ADVANCED_TURTLE_MODEL = ResourceLocation.fromNamespaceAndPath(ComputerCraftAPI.MOD_ID, "block/turtle_advanced");
|
||||
public static final ResourceLocation COLOUR_TURTLE_MODEL = ResourceLocation.fromNamespaceAndPath(ComputerCraftAPI.MOD_ID, "block/turtle_colour");
|
||||
|
||||
private final BlockEntityRenderDispatcher renderer;
|
||||
@@ -72,9 +77,6 @@ public class TurtleBlockEntityRenderer implements BlockEntityRenderer<TurtleBloc
|
||||
transform.translate(0.5f, 0.5f, 0.5f);
|
||||
var yaw = turtle.getRenderYaw(partialTicks);
|
||||
transform.mulPose(Axis.YP.rotationDegrees(180.0f - yaw));
|
||||
if (label != null && (label.equals("Dinnerbone") || label.equals("Grumm"))) {
|
||||
transform.scale(1.0f, -1.0f, 1.0f);
|
||||
}
|
||||
transform.translate(-0.5f, -0.5f, -0.5f);
|
||||
|
||||
// Render the turtle
|
||||
@@ -82,14 +84,10 @@ public class TurtleBlockEntityRenderer implements BlockEntityRenderer<TurtleBloc
|
||||
var overlay = turtle.getOverlay();
|
||||
|
||||
if (colour == -1) {
|
||||
// Render the turtle using its item model.
|
||||
var modelManager = Minecraft.getInstance().getItemRenderer().getItemModelShaper();
|
||||
var model = modelManager.getItemModel(turtle.getBlockState().getBlock().asItem());
|
||||
if (model == null) model = modelManager.getModelManager().getMissingModel();
|
||||
renderModel(transform, buffers, lightmapCoord, overlayLight, model, null);
|
||||
renderModel(transform, buffers, lightmapCoord, overlayLight, turtle.getFamily() == ComputerFamily.NORMAL ? NORMAL_TURTLE_MODEL : ADVANCED_TURTLE_MODEL, null);
|
||||
} else {
|
||||
// Otherwise render it using the colour item.
|
||||
renderModel(transform, buffers, lightmapCoord, overlayLight, COLOUR_TURTLE_MODEL, new int[]{ FastColor.ARGB32.opaque(colour) });
|
||||
renderModel(transform, buffers, lightmapCoord, overlayLight, COLOUR_TURTLE_MODEL, new int[]{ ARGB.opaque(colour) });
|
||||
}
|
||||
|
||||
// Render the overlay
|
||||
@@ -116,15 +114,25 @@ public class TurtleBlockEntityRenderer implements BlockEntityRenderer<TurtleBloc
|
||||
transform.mulPose(Axis.XN.rotationDegrees(toolAngle));
|
||||
transform.translate(0.0f, -0.5f, -0.5f);
|
||||
|
||||
var model = TurtleUpgradeModellers.getModel(upgrade, turtle.getAccess(), side);
|
||||
applyTransformation(transform, model.matrix());
|
||||
renderModel(transform, buffers, lightmapCoord, overlayLight, model.model(), null);
|
||||
switch (TurtleUpgradeModellers.getModel(upgrade, turtle.getAccess(), side)) {
|
||||
case TransformedModel.Item model -> {
|
||||
transform.mulPose(model.transformation().getMatrix());
|
||||
transform.mulPose(Axis.YP.rotation(Mth.PI));
|
||||
Minecraft.getInstance().getItemRenderer().renderStatic(
|
||||
model.stack(), ItemDisplayContext.FIXED, lightmapCoord, overlayLight, transform, buffers, turtle.getLevel(), 0
|
||||
);
|
||||
}
|
||||
|
||||
case TransformedModel.Baked model ->
|
||||
renderModel(transform, buffers, lightmapCoord, overlayLight, model.model(), null);
|
||||
}
|
||||
|
||||
|
||||
transform.popPose();
|
||||
}
|
||||
|
||||
private void renderModel(PoseStack transform, MultiBufferSource buffers, int lightmapCoord, int overlayLight, ResourceLocation modelLocation, int @Nullable [] tints) {
|
||||
var modelManager = Minecraft.getInstance().getItemRenderer().getItemModelShaper().getModelManager();
|
||||
var modelManager = Minecraft.getInstance().getModelManager();
|
||||
renderModel(transform, buffers, lightmapCoord, overlayLight, ClientPlatformHelper.get().getModel(modelManager, modelLocation), tints);
|
||||
}
|
||||
|
||||
@@ -142,16 +150,4 @@ public class TurtleBlockEntityRenderer implements BlockEntityRenderer<TurtleBloc
|
||||
private void renderModel(PoseStack transform, MultiBufferSource renderer, int lightmapCoord, int overlayLight, BakedModel model, int @Nullable [] tints) {
|
||||
ClientPlatformHelper.get().renderBakedModel(transform, renderer, model, lightmapCoord, overlayLight, tints);
|
||||
}
|
||||
|
||||
private static void applyTransformation(PoseStack stack, Transformation transformation) {
|
||||
var trans = transformation.getTranslation();
|
||||
stack.translate(trans.x(), trans.y(), trans.z());
|
||||
|
||||
stack.mulPose(transformation.getLeftRotation());
|
||||
|
||||
var scale = transformation.getScale();
|
||||
stack.scale(scale.x(), scale.y(), scale.z());
|
||||
|
||||
stack.mulPose(transformation.getRightRotation());
|
||||
}
|
||||
}
|
||||
|
@@ -4,40 +4,28 @@
|
||||
|
||||
package dan200.computercraft.client.render.monitor;
|
||||
|
||||
import com.mojang.blaze3d.platform.GlStateManager;
|
||||
import com.mojang.blaze3d.systems.RenderSystem;
|
||||
import com.mojang.blaze3d.vertex.PoseStack;
|
||||
import com.mojang.blaze3d.vertex.Tesselator;
|
||||
import com.mojang.blaze3d.vertex.VertexBuffer;
|
||||
import com.mojang.blaze3d.vertex.VertexConsumer;
|
||||
import com.mojang.blaze3d.vertex.*;
|
||||
import com.mojang.math.Axis;
|
||||
import dan200.computercraft.annotations.ForgeOverride;
|
||||
import dan200.computercraft.client.FrameInfo;
|
||||
import dan200.computercraft.client.integration.ShaderMod;
|
||||
import dan200.computercraft.client.render.RenderTypes;
|
||||
import dan200.computercraft.client.render.text.DirectFixedWidthFontRenderer;
|
||||
import dan200.computercraft.client.render.text.FixedWidthFontRenderer;
|
||||
import dan200.computercraft.client.render.vbo.DirectBuffers;
|
||||
import dan200.computercraft.client.render.vbo.DirectVertexBuffer;
|
||||
import dan200.computercraft.core.terminal.Terminal;
|
||||
import dan200.computercraft.core.util.Nullability;
|
||||
import dan200.computercraft.shared.config.Config;
|
||||
import dan200.computercraft.shared.peripheral.monitor.ClientMonitor;
|
||||
import dan200.computercraft.shared.peripheral.monitor.MonitorBlockEntity;
|
||||
import dan200.computercraft.shared.peripheral.monitor.MonitorRenderer;
|
||||
import dan200.computercraft.shared.util.DirectionUtil;
|
||||
import net.minecraft.client.renderer.CompiledShaderProgram;
|
||||
import net.minecraft.client.renderer.FogParameters;
|
||||
import net.minecraft.client.renderer.MultiBufferSource;
|
||||
import net.minecraft.client.renderer.blockentity.BlockEntityRenderer;
|
||||
import net.minecraft.client.renderer.blockentity.BlockEntityRendererProvider;
|
||||
import net.minecraft.world.phys.AABB;
|
||||
import org.joml.Matrix4f;
|
||||
import org.jspecify.annotations.Nullable;
|
||||
import org.lwjgl.opengl.GL11;
|
||||
import org.lwjgl.opengl.GL20;
|
||||
import org.lwjgl.opengl.GL31;
|
||||
import org.lwjgl.system.MemoryUtil;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
import static dan200.computercraft.client.render.text.FixedWidthFontRenderer.FONT_HEIGHT;
|
||||
@@ -51,9 +39,7 @@ public class MonitorBlockEntityRenderer implements BlockEntityRenderer<MonitorBl
|
||||
*/
|
||||
private static final float MARGIN = (float) (MonitorBlockEntity.RENDER_MARGIN * 1.1);
|
||||
|
||||
private static @Nullable ByteBuffer backingBuffer;
|
||||
|
||||
private static long lastFrame = -1;
|
||||
private static final ByteBufferBuilder backingBufferBuilder = new ByteBufferBuilder(0x4000);
|
||||
|
||||
public MonitorBlockEntityRenderer(BlockEntityRendererProvider.Context context) {
|
||||
}
|
||||
@@ -76,7 +62,6 @@ public class MonitorBlockEntityRenderer implements BlockEntityRenderer<MonitorBl
|
||||
return;
|
||||
}
|
||||
|
||||
lastFrame = renderFrame;
|
||||
renderState.lastRenderFrame = renderFrame;
|
||||
renderState.lastRenderPos = monitorPos;
|
||||
|
||||
@@ -124,7 +109,7 @@ public class MonitorBlockEntityRenderer implements BlockEntityRenderer<MonitorBl
|
||||
transform.popPose();
|
||||
} else {
|
||||
FixedWidthFontRenderer.drawEmptyTerminal(
|
||||
FixedWidthFontRenderer.toVertexConsumer(transform, bufferSource.getBuffer(RenderTypes.TERMINAL)),
|
||||
FixedWidthFontRenderer.toVertexConsumer(transform, bufferSource.getBuffer(FixedWidthFontRenderer.TERMINAL_TEXT)),
|
||||
-MARGIN, MARGIN,
|
||||
(float) (xSize + 2 * MARGIN), (float) -(ySize + MARGIN * 2)
|
||||
);
|
||||
@@ -136,126 +121,100 @@ public class MonitorBlockEntityRenderer implements BlockEntityRenderer<MonitorBl
|
||||
private static void renderTerminal(
|
||||
Matrix4f matrix, ClientMonitor monitor, MonitorRenderState renderState, Terminal terminal, float xMargin, float yMargin
|
||||
) {
|
||||
int width = terminal.getWidth(), height = terminal.getHeight();
|
||||
int pixelWidth = width * FONT_WIDTH, pixelHeight = height * FONT_HEIGHT;
|
||||
|
||||
var renderType = currentRenderer();
|
||||
var redraw = monitor.pollTerminalChanged();
|
||||
if (renderState.createBuffer(renderType)) redraw = true;
|
||||
if (renderState.createBuffer()) redraw = true;
|
||||
|
||||
switch (renderType) {
|
||||
case TBO -> {
|
||||
if (redraw) {
|
||||
var terminalBuffer = getBuffer(width * height * 3);
|
||||
MonitorTextureBufferShader.setTerminalData(terminalBuffer, terminal);
|
||||
DirectBuffers.setBufferData(GL31.GL_TEXTURE_BUFFER, renderState.tboBuffer, terminalBuffer, GL20.GL_STATIC_DRAW);
|
||||
var backgroundBuffer = assertNonNull(renderState.backgroundBuffer);
|
||||
var foregroundBuffer = assertNonNull(renderState.foregroundBuffer);
|
||||
if (redraw) {
|
||||
var size = DirectFixedWidthFontRenderer.getVertexCount(terminal);
|
||||
|
||||
var uniformBuffer = getBuffer(MonitorTextureBufferShader.UNIFORM_SIZE);
|
||||
MonitorTextureBufferShader.setUniformData(uniformBuffer, terminal);
|
||||
DirectBuffers.setBufferData(GL31.GL_UNIFORM_BUFFER, renderState.tboUniform, uniformBuffer, GL20.GL_STATIC_DRAW);
|
||||
}
|
||||
// In an ideal world we could upload these both into one buffer. However, we can't render VBOs with
|
||||
// a starting and ending offset, and so need to use two buffers instead.
|
||||
|
||||
// Nobody knows what they're doing!
|
||||
var active = GlStateManager._getActiveTexture();
|
||||
RenderSystem.activeTexture(MonitorTextureBufferShader.TEXTURE_INDEX);
|
||||
GL11.glBindTexture(GL31.GL_TEXTURE_BUFFER, renderState.tboTexture);
|
||||
RenderSystem.activeTexture(active);
|
||||
renderToBuffer(backgroundBuffer, size, sink ->
|
||||
DirectFixedWidthFontRenderer.drawTerminalBackground(sink, 0, 0, terminal, yMargin, yMargin, xMargin, xMargin));
|
||||
|
||||
var shader = RenderTypes.getMonitorTextureBufferShader();
|
||||
shader.setupUniform(renderState.tboUniform);
|
||||
|
||||
var buffer = Tesselator.getInstance().begin(RenderTypes.MONITOR_TBO.mode(), RenderTypes.MONITOR_TBO.format());
|
||||
tboVertex(buffer, matrix, -xMargin, -yMargin);
|
||||
tboVertex(buffer, matrix, -xMargin, pixelHeight + yMargin);
|
||||
tboVertex(buffer, matrix, pixelWidth + xMargin, -yMargin);
|
||||
tboVertex(buffer, matrix, pixelWidth + xMargin, pixelHeight + yMargin);
|
||||
RenderTypes.MONITOR_TBO.draw(Nullability.assertNonNull(buffer.build()));
|
||||
}
|
||||
case VBO -> {
|
||||
var backgroundBuffer = assertNonNull(renderState.backgroundBuffer);
|
||||
var foregroundBuffer = assertNonNull(renderState.foregroundBuffer);
|
||||
if (redraw) {
|
||||
var size = DirectFixedWidthFontRenderer.getVertexCount(terminal);
|
||||
|
||||
// In an ideal world we could upload these both into one buffer. However, we can't render VBOs with
|
||||
// a starting and ending offset, and so need to use two buffers instead.
|
||||
|
||||
renderToBuffer(backgroundBuffer, size, sink ->
|
||||
DirectFixedWidthFontRenderer.drawTerminalBackground(sink, 0, 0, terminal, yMargin, yMargin, xMargin, xMargin));
|
||||
|
||||
renderToBuffer(foregroundBuffer, size, sink -> {
|
||||
DirectFixedWidthFontRenderer.drawTerminalForeground(sink, 0, 0, terminal);
|
||||
// If the cursor is visible, we append it to the end of our buffer. When rendering, we can either
|
||||
// render n or n+1 quads and so toggle the cursor on and off.
|
||||
DirectFixedWidthFontRenderer.drawCursor(sink, 0, 0, terminal);
|
||||
});
|
||||
}
|
||||
|
||||
// Our VBO renders coordinates in monitor-space rather than world space. A full sized monitor (8x6) will
|
||||
// use positions from (0, 0) to (164*FONT_WIDTH, 81*FONT_HEIGHT) = (984, 729). This is far outside the
|
||||
// normal render distance (~200), and the edges of the monitor fade out due to fog.
|
||||
// There's not really a good way around this, at least without using a custom render type (which the VBO
|
||||
// renderer is trying to avoid!). Instead, we just disable fog entirely by setting the fog start to an
|
||||
// absurdly high value.
|
||||
var oldFogStart = RenderSystem.getShaderFogStart();
|
||||
RenderSystem.setShaderFogStart(1e4f);
|
||||
|
||||
RenderTypes.TERMINAL.setupRenderState();
|
||||
|
||||
// Compose the existing model view matrix with our transformation matrix.
|
||||
var modelView = new Matrix4f(RenderSystem.getModelViewMatrix()).mul(matrix);
|
||||
|
||||
// Render background geometry
|
||||
backgroundBuffer.bind();
|
||||
backgroundBuffer.drawWithShader(modelView, RenderSystem.getProjectionMatrix(), RenderTypes.getTerminalShader());
|
||||
|
||||
// Render foreground geometry with glPolygonOffset enabled.
|
||||
RenderSystem.polygonOffset(-1.0f, -10.0f);
|
||||
RenderSystem.enablePolygonOffset();
|
||||
|
||||
foregroundBuffer.bind();
|
||||
foregroundBuffer.drawWithShader(
|
||||
modelView, RenderSystem.getProjectionMatrix(), RenderTypes.getTerminalShader(),
|
||||
// Skip the cursor quad if it is not visible this frame.
|
||||
FixedWidthFontRenderer.isCursorVisible(terminal) && !FrameInfo.getGlobalCursorBlink()
|
||||
? foregroundBuffer.getIndexCount() - RenderTypes.TERMINAL.mode().indexCount(4)
|
||||
: foregroundBuffer.getIndexCount()
|
||||
);
|
||||
|
||||
// Clear state
|
||||
RenderSystem.polygonOffset(0.0f, -0.0f);
|
||||
RenderSystem.disablePolygonOffset();
|
||||
RenderTypes.TERMINAL.clearRenderState();
|
||||
VertexBuffer.unbind();
|
||||
|
||||
RenderSystem.setShaderFogStart(oldFogStart);
|
||||
}
|
||||
case BEST -> throw new IllegalStateException("Impossible: Should never use BEST renderer");
|
||||
renderToBuffer(foregroundBuffer, size + 4, sink -> {
|
||||
DirectFixedWidthFontRenderer.drawTerminalForeground(sink, 0, 0, terminal);
|
||||
// If the cursor is visible, we append it to the end of our buffer. When rendering, we can either
|
||||
// render n or n+1 quads and so toggle the cursor on and off.
|
||||
DirectFixedWidthFontRenderer.drawCursor(sink, 0, 0, terminal);
|
||||
});
|
||||
}
|
||||
|
||||
// Our VBO renders coordinates in monitor-space rather than world space. A full sized monitor (8x6) will
|
||||
// use positions from (0, 0) to (164*FONT_WIDTH, 81*FONT_HEIGHT) = (984, 729). This is far outside the
|
||||
// normal render distance (~200), and the edges of the monitor fade out due to fog.
|
||||
// There's not really a good way around this, at least without using a custom render type (which the VBO
|
||||
// renderer is trying to avoid!). Instead, we just disable fog entirely by setting the fog start to an
|
||||
// absurdly high value.
|
||||
var oldFog = RenderSystem.getShaderFog();
|
||||
RenderSystem.setShaderFog(FogParameters.NO_FOG);
|
||||
|
||||
FixedWidthFontRenderer.TERMINAL_TEXT.setupRenderState();
|
||||
|
||||
// Compose the existing model view matrix with our transformation matrix.
|
||||
var modelView = new Matrix4f(RenderSystem.getModelViewMatrix()).mul(matrix);
|
||||
|
||||
// Render background geometry
|
||||
backgroundBuffer.bind();
|
||||
backgroundBuffer.drawWithShader(modelView, RenderSystem.getProjectionMatrix(), RenderSystem.getShader());
|
||||
|
||||
// Render foreground geometry with glPolygonOffset enabled.
|
||||
RenderSystem.polygonOffset(-1.0f, -10.0f);
|
||||
RenderSystem.enablePolygonOffset();
|
||||
|
||||
foregroundBuffer.bind();
|
||||
drawWithShader(
|
||||
foregroundBuffer, modelView, RenderSystem.getProjectionMatrix(), RenderSystem.getShader(),
|
||||
// Skip the cursor quad if it is not visible this frame.
|
||||
FixedWidthFontRenderer.isCursorVisible(terminal) && !FrameInfo.getGlobalCursorBlink()
|
||||
? foregroundBuffer.indexCount - FixedWidthFontRenderer.TERMINAL_TEXT.mode().indexCount(4)
|
||||
: foregroundBuffer.indexCount
|
||||
);
|
||||
|
||||
// Clear state
|
||||
RenderSystem.polygonOffset(0.0f, -0.0f);
|
||||
RenderSystem.disablePolygonOffset();
|
||||
FixedWidthFontRenderer.TERMINAL_TEXT.clearRenderState();
|
||||
VertexBuffer.unbind();
|
||||
|
||||
RenderSystem.setShaderFog(oldFog);
|
||||
}
|
||||
|
||||
private static void renderToBuffer(DirectVertexBuffer vbo, int size, Consumer<DirectFixedWidthFontRenderer.QuadEmitter> draw) {
|
||||
var sink = ShaderMod.get().getQuadEmitter(size, MonitorBlockEntityRenderer::getBuffer);
|
||||
var buffer = sink.buffer();
|
||||
|
||||
private static void renderToBuffer(VertexBuffer vbo, int size, Consumer<DirectFixedWidthFontRenderer.QuadEmitter> draw) {
|
||||
var sink = ShaderMod.get().getQuadEmitter(size, backingBufferBuilder);
|
||||
draw.accept(sink);
|
||||
buffer.flip();
|
||||
vbo.upload(buffer.limit() / sink.format().getVertexSize(), RenderTypes.TERMINAL.mode(), sink.format(), buffer);
|
||||
}
|
||||
|
||||
private static void tboVertex(VertexConsumer builder, Matrix4f matrix, float x, float y) {
|
||||
// We encode position in the UV, as that's not transformed by the matrix.
|
||||
builder.addVertex(matrix, x, y, 0).setUv(x, y);
|
||||
}
|
||||
|
||||
private static ByteBuffer getBuffer(int capacity) {
|
||||
var buffer = backingBuffer;
|
||||
if (buffer == null || buffer.capacity() < capacity) {
|
||||
buffer = backingBuffer = buffer == null ? MemoryUtil.memAlloc(capacity) : MemoryUtil.memRealloc(buffer, capacity);
|
||||
var result = backingBufferBuilder.build();
|
||||
if (result == null) {
|
||||
// If we have nothing to draw, just mark it as empty. We'll skip drawing in drawWithShader.
|
||||
vbo.indexCount = 0;
|
||||
return;
|
||||
}
|
||||
|
||||
buffer.clear();
|
||||
return buffer;
|
||||
var buffer = result.byteBuffer();
|
||||
var vertices = buffer.limit() / sink.format().getVertexSize();
|
||||
|
||||
vbo.bind();
|
||||
vbo.upload(new MeshData(result, new MeshData.DrawState(
|
||||
sink.format(),
|
||||
vertices, FixedWidthFontRenderer.TERMINAL_TEXT.mode().indexCount(vertices),
|
||||
FixedWidthFontRenderer.TERMINAL_TEXT.mode(), VertexFormat.IndexType.least(vertices)
|
||||
)));
|
||||
}
|
||||
|
||||
private static void drawWithShader(VertexBuffer buffer, Matrix4f modelView, Matrix4f projection, @Nullable CompiledShaderProgram compiledShaderProgram, int indicies) {
|
||||
var originalIndexCount = buffer.indexCount;
|
||||
if (originalIndexCount == 0) return;
|
||||
|
||||
try {
|
||||
buffer.indexCount = indicies;
|
||||
buffer.drawWithShader(modelView, projection, compiledShaderProgram);
|
||||
} finally {
|
||||
buffer.indexCount = originalIndexCount;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -267,28 +226,4 @@ public class MonitorBlockEntityRenderer implements BlockEntityRenderer<MonitorBl
|
||||
public AABB getRenderBoundingBox(MonitorBlockEntity monitor) {
|
||||
return monitor.getRenderBoundingBox();
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if any monitors were rendered this frame.
|
||||
*
|
||||
* @return Whether any monitors were rendered.
|
||||
*/
|
||||
public static boolean hasRenderedThisFrame() {
|
||||
return FrameInfo.getRenderFrame() == lastFrame;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the current renderer to use.
|
||||
*
|
||||
* @return The current renderer. Will not return {@link MonitorRenderer#BEST}.
|
||||
*/
|
||||
public static MonitorRenderer currentRenderer() {
|
||||
var current = Config.monitorRenderer;
|
||||
if (current == MonitorRenderer.BEST) current = Config.monitorRenderer = bestRenderer();
|
||||
return current;
|
||||
}
|
||||
|
||||
private static MonitorRenderer bestRenderer() {
|
||||
return MonitorRenderer.VBO;
|
||||
}
|
||||
}
|
||||
|
@@ -6,13 +6,12 @@ package dan200.computercraft.client.render.monitor;
|
||||
|
||||
import com.mojang.blaze3d.vertex.PoseStack;
|
||||
import com.mojang.blaze3d.vertex.VertexConsumer;
|
||||
import dan200.computercraft.client.render.BlockOutlineRenderer;
|
||||
import dan200.computercraft.shared.peripheral.monitor.MonitorBlockEntity;
|
||||
import net.minecraft.client.Camera;
|
||||
import net.minecraft.client.renderer.MultiBufferSource;
|
||||
import net.minecraft.client.renderer.RenderType;
|
||||
import net.minecraft.core.Direction;
|
||||
import net.minecraft.world.phys.BlockHitResult;
|
||||
import org.joml.Matrix4f;
|
||||
|
||||
import java.util.EnumSet;
|
||||
|
||||
@@ -33,8 +32,7 @@ public final class MonitorHighlightRenderer {
|
||||
var world = camera.getEntity().getCommandSenderWorld();
|
||||
var pos = hit.getBlockPos();
|
||||
|
||||
var tile = world.getBlockEntity(pos);
|
||||
if (!(tile instanceof MonitorBlockEntity monitor)) return false;
|
||||
if (!(world.getBlockEntity(pos) instanceof MonitorBlockEntity monitor)) return false;
|
||||
|
||||
// Determine which sides are part of the external faces of the monitor, and so which need to be rendered.
|
||||
var faces = EnumSet.allOf(Direction.class);
|
||||
@@ -49,39 +47,37 @@ public final class MonitorHighlightRenderer {
|
||||
transformStack.pushPose();
|
||||
transformStack.translate(pos.getX() - cameraPos.x(), pos.getY() - cameraPos.y(), pos.getZ() - cameraPos.z());
|
||||
|
||||
// I wish I could think of a better way to do this
|
||||
var buffer = bufferSource.getBuffer(RenderType.lines());
|
||||
var transform = transformStack.last().pose();
|
||||
var normal = transformStack.last();
|
||||
if (faces.contains(NORTH) || faces.contains(WEST)) line(buffer, transform, normal, 0, 0, 0, UP);
|
||||
if (faces.contains(SOUTH) || faces.contains(WEST)) line(buffer, transform, normal, 0, 0, 1, UP);
|
||||
if (faces.contains(NORTH) || faces.contains(EAST)) line(buffer, transform, normal, 1, 0, 0, UP);
|
||||
if (faces.contains(SOUTH) || faces.contains(EAST)) line(buffer, transform, normal, 1, 0, 1, UP);
|
||||
if (faces.contains(NORTH) || faces.contains(DOWN)) line(buffer, transform, normal, 0, 0, 0, EAST);
|
||||
if (faces.contains(SOUTH) || faces.contains(DOWN)) line(buffer, transform, normal, 0, 0, 1, EAST);
|
||||
if (faces.contains(NORTH) || faces.contains(UP)) line(buffer, transform, normal, 0, 1, 0, EAST);
|
||||
if (faces.contains(SOUTH) || faces.contains(UP)) line(buffer, transform, normal, 0, 1, 1, EAST);
|
||||
if (faces.contains(WEST) || faces.contains(DOWN)) line(buffer, transform, normal, 0, 0, 0, SOUTH);
|
||||
if (faces.contains(EAST) || faces.contains(DOWN)) line(buffer, transform, normal, 1, 0, 0, SOUTH);
|
||||
if (faces.contains(WEST) || faces.contains(UP)) line(buffer, transform, normal, 0, 1, 0, SOUTH);
|
||||
if (faces.contains(EAST) || faces.contains(UP)) line(buffer, transform, normal, 1, 1, 0, SOUTH);
|
||||
var transform = transformStack.last();
|
||||
BlockOutlineRenderer.render(bufferSource, (buffer, colour) -> draw(buffer, transform, faces, colour));
|
||||
|
||||
transformStack.popPose();
|
||||
return true;
|
||||
}
|
||||
|
||||
private static void line(VertexConsumer buffer, Matrix4f transform, PoseStack.Pose normal, float x, float y, float z, Direction direction) {
|
||||
private static void draw(VertexConsumer buffer, PoseStack.Pose transform, EnumSet<Direction> faces, int colour) {
|
||||
// I wish I could think of a better way to do this
|
||||
if (faces.contains(NORTH) || faces.contains(WEST)) line(buffer, transform, 0, 0, 0, UP, colour);
|
||||
if (faces.contains(SOUTH) || faces.contains(WEST)) line(buffer, transform, 0, 0, 1, UP, colour);
|
||||
if (faces.contains(NORTH) || faces.contains(EAST)) line(buffer, transform, 1, 0, 0, UP, colour);
|
||||
if (faces.contains(SOUTH) || faces.contains(EAST)) line(buffer, transform, 1, 0, 1, UP, colour);
|
||||
if (faces.contains(NORTH) || faces.contains(DOWN)) line(buffer, transform, 0, 0, 0, EAST, colour);
|
||||
if (faces.contains(SOUTH) || faces.contains(DOWN)) line(buffer, transform, 0, 0, 1, EAST, colour);
|
||||
if (faces.contains(NORTH) || faces.contains(UP)) line(buffer, transform, 0, 1, 0, EAST, colour);
|
||||
if (faces.contains(SOUTH) || faces.contains(UP)) line(buffer, transform, 0, 1, 1, EAST, colour);
|
||||
if (faces.contains(WEST) || faces.contains(DOWN)) line(buffer, transform, 0, 0, 0, SOUTH, colour);
|
||||
if (faces.contains(EAST) || faces.contains(DOWN)) line(buffer, transform, 1, 0, 0, SOUTH, colour);
|
||||
if (faces.contains(WEST) || faces.contains(UP)) line(buffer, transform, 0, 1, 0, SOUTH, colour);
|
||||
if (faces.contains(EAST) || faces.contains(UP)) line(buffer, transform, 1, 1, 0, SOUTH, colour);
|
||||
}
|
||||
|
||||
private static void line(VertexConsumer buffer, PoseStack.Pose transform, float x, float y, float z, Direction direction, int colour) {
|
||||
buffer
|
||||
.addVertex(transform, x, y, z)
|
||||
.setColor(0, 0, 0, 0.4f)
|
||||
.setNormal(normal, direction.getStepX(), direction.getStepY(), direction.getStepZ());
|
||||
.setColor(colour)
|
||||
.setNormal(transform, direction.getStepX(), direction.getStepY(), direction.getStepZ());
|
||||
buffer
|
||||
.addVertex(transform,
|
||||
x + direction.getStepX(),
|
||||
y + direction.getStepY(),
|
||||
z + direction.getStepZ()
|
||||
)
|
||||
.setColor(0, 0, 0, 0.4f)
|
||||
.setNormal(normal, direction.getStepX(), direction.getStepY(), direction.getStepZ());
|
||||
.addVertex(transform, x + direction.getStepX(), y + direction.getStepY(), z + direction.getStepZ())
|
||||
.setColor(colour)
|
||||
.setNormal(transform, direction.getStepX(), direction.getStepY(), direction.getStepZ());
|
||||
}
|
||||
}
|
||||
|
@@ -5,17 +5,11 @@
|
||||
package dan200.computercraft.client.render.monitor;
|
||||
|
||||
import com.google.errorprone.annotations.concurrent.GuardedBy;
|
||||
import com.mojang.blaze3d.platform.GlStateManager;
|
||||
import dan200.computercraft.client.render.vbo.DirectBuffers;
|
||||
import dan200.computercraft.client.render.vbo.DirectVertexBuffer;
|
||||
import com.mojang.blaze3d.buffers.BufferUsage;
|
||||
import com.mojang.blaze3d.vertex.VertexBuffer;
|
||||
import dan200.computercraft.shared.peripheral.monitor.ClientMonitor;
|
||||
import dan200.computercraft.shared.peripheral.monitor.MonitorRenderer;
|
||||
import net.minecraft.core.BlockPos;
|
||||
import org.jspecify.annotations.Nullable;
|
||||
import org.lwjgl.opengl.GL11;
|
||||
import org.lwjgl.opengl.GL15;
|
||||
import org.lwjgl.opengl.GL30;
|
||||
import org.lwjgl.opengl.GL31;
|
||||
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
@@ -34,52 +28,23 @@ public class MonitorRenderState implements ClientMonitor.RenderState {
|
||||
public long lastRenderFrame = -1;
|
||||
public @Nullable BlockPos lastRenderPos = null;
|
||||
|
||||
public int tboBuffer;
|
||||
public int tboTexture;
|
||||
public int tboUniform;
|
||||
public @Nullable DirectVertexBuffer backgroundBuffer;
|
||||
public @Nullable DirectVertexBuffer foregroundBuffer;
|
||||
public @Nullable VertexBuffer backgroundBuffer;
|
||||
public @Nullable VertexBuffer foregroundBuffer;
|
||||
|
||||
/**
|
||||
* Create the appropriate buffer if needed.
|
||||
*
|
||||
* @param renderer The renderer to use.
|
||||
* @return If a buffer was created. This will return {@code false} if we already have an appropriate buffer,
|
||||
* or this mode does not require one.
|
||||
*/
|
||||
public boolean createBuffer(MonitorRenderer renderer) {
|
||||
switch (renderer) {
|
||||
case TBO: {
|
||||
if (tboBuffer != 0) return false;
|
||||
public boolean createBuffer() {
|
||||
if (backgroundBuffer != null) return false;
|
||||
|
||||
deleteBuffers();
|
||||
|
||||
tboBuffer = DirectBuffers.createBuffer();
|
||||
DirectBuffers.setEmptyBufferData(GL31.GL_TEXTURE_BUFFER, tboBuffer, GL15.GL_STATIC_DRAW);
|
||||
tboTexture = GlStateManager._genTexture();
|
||||
GL11.glBindTexture(GL31.GL_TEXTURE_BUFFER, tboTexture);
|
||||
GL31.glTexBuffer(GL31.GL_TEXTURE_BUFFER, GL30.GL_R8UI, tboBuffer);
|
||||
GL11.glBindTexture(GL31.GL_TEXTURE_BUFFER, 0);
|
||||
|
||||
tboUniform = DirectBuffers.createBuffer();
|
||||
DirectBuffers.setEmptyBufferData(GL31.GL_UNIFORM_BUFFER, tboUniform, GL15.GL_STATIC_DRAW);
|
||||
|
||||
addMonitor();
|
||||
return true;
|
||||
}
|
||||
|
||||
case VBO:
|
||||
if (backgroundBuffer != null) return false;
|
||||
|
||||
deleteBuffers();
|
||||
backgroundBuffer = new DirectVertexBuffer();
|
||||
foregroundBuffer = new DirectVertexBuffer();
|
||||
addMonitor();
|
||||
return true;
|
||||
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
deleteBuffers();
|
||||
backgroundBuffer = new VertexBuffer(BufferUsage.STATIC_WRITE);
|
||||
foregroundBuffer = new VertexBuffer(BufferUsage.STATIC_WRITE);
|
||||
addMonitor();
|
||||
return true;
|
||||
}
|
||||
|
||||
private void addMonitor() {
|
||||
@@ -89,21 +54,6 @@ public class MonitorRenderState implements ClientMonitor.RenderState {
|
||||
}
|
||||
|
||||
private void deleteBuffers() {
|
||||
if (tboBuffer != 0) {
|
||||
DirectBuffers.deleteBuffer(GL31.GL_TEXTURE_BUFFER, tboBuffer);
|
||||
tboBuffer = 0;
|
||||
}
|
||||
|
||||
if (tboTexture != 0) {
|
||||
GlStateManager._deleteTexture(tboTexture);
|
||||
tboTexture = 0;
|
||||
}
|
||||
|
||||
if (tboUniform != 0) {
|
||||
DirectBuffers.deleteBuffer(GL31.GL_UNIFORM_BUFFER, tboUniform);
|
||||
tboUniform = 0;
|
||||
}
|
||||
|
||||
if (backgroundBuffer != null) {
|
||||
backgroundBuffer.close();
|
||||
backgroundBuffer = null;
|
||||
@@ -117,7 +67,7 @@ public class MonitorRenderState implements ClientMonitor.RenderState {
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
if (tboBuffer != 0 || backgroundBuffer != null) {
|
||||
if (backgroundBuffer != null) {
|
||||
synchronized (allMonitors) {
|
||||
allMonitors.remove(this);
|
||||
}
|
||||
|
@@ -1,127 +0,0 @@
|
||||
// SPDX-FileCopyrightText: 2020 The CC: Tweaked Developers
|
||||
//
|
||||
// SPDX-License-Identifier: MPL-2.0
|
||||
|
||||
package dan200.computercraft.client.render.monitor;
|
||||
|
||||
import com.mojang.blaze3d.shaders.Uniform;
|
||||
import com.mojang.blaze3d.vertex.VertexFormat;
|
||||
import dan200.computercraft.client.FrameInfo;
|
||||
import dan200.computercraft.client.render.RenderTypes;
|
||||
import dan200.computercraft.client.render.text.FixedWidthFontRenderer;
|
||||
import dan200.computercraft.core.terminal.Terminal;
|
||||
import dan200.computercraft.core.terminal.TextBuffer;
|
||||
import dan200.computercraft.core.util.Colour;
|
||||
import net.minecraft.client.renderer.ShaderInstance;
|
||||
import net.minecraft.server.packs.resources.ResourceProvider;
|
||||
import org.jspecify.annotations.Nullable;
|
||||
import org.lwjgl.opengl.GL13;
|
||||
import org.lwjgl.opengl.GL31;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.ByteBuffer;
|
||||
|
||||
import static dan200.computercraft.client.render.text.FixedWidthFontRenderer.getColour;
|
||||
|
||||
/**
|
||||
* The shader used for the monitor TBO renderer.
|
||||
* <p>
|
||||
* This extends Minecraft's default shader loading code to extract out the TBO buffer and handle our custom uniforms
|
||||
* ({@code MonitorData}, {@code CursorBlink}).
|
||||
* <p>
|
||||
* See also {@code monitor_tbo.fsh} and {@code monitor_tbo.vsh} in the mod's resources.
|
||||
*
|
||||
* @see RenderTypes#getMonitorTextureBufferShader()
|
||||
*/
|
||||
public class MonitorTextureBufferShader extends ShaderInstance {
|
||||
private static final Logger LOG = LoggerFactory.getLogger(MonitorTextureBufferShader.class);
|
||||
|
||||
public static final int UNIFORM_SIZE = 4 * 4 * 16 + 4 + 4 + 2 * 4 + 4;
|
||||
|
||||
static final int TEXTURE_INDEX = GL13.GL_TEXTURE3;
|
||||
|
||||
private final int monitorData;
|
||||
private int uniformBuffer = 0;
|
||||
|
||||
private final @Nullable Uniform cursorBlink;
|
||||
|
||||
public MonitorTextureBufferShader(ResourceProvider provider, String location, VertexFormat format) throws IOException {
|
||||
super(provider, location, format);
|
||||
monitorData = GL31.glGetUniformBlockIndex(getId(), "MonitorData");
|
||||
if (monitorData == -1) throw new IllegalStateException("Could not find MonitorData uniform.");
|
||||
|
||||
cursorBlink = getUniformChecked("CursorBlink");
|
||||
|
||||
var tbo = getUniformChecked("Tbo");
|
||||
if (tbo != null) tbo.set(TEXTURE_INDEX - GL13.GL_TEXTURE0);
|
||||
}
|
||||
|
||||
public void setupUniform(int buffer) {
|
||||
uniformBuffer = buffer;
|
||||
|
||||
var cursorAlpha = FrameInfo.getGlobalCursorBlink() ? 1 : 0;
|
||||
if (cursorBlink != null && cursorBlink.getIntBuffer().get(0) != cursorAlpha) cursorBlink.set(cursorAlpha);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void apply() {
|
||||
super.apply();
|
||||
GL31.glBindBufferBase(GL31.GL_UNIFORM_BUFFER, monitorData, uniformBuffer);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private Uniform getUniformChecked(String name) {
|
||||
var uniform = getUniform(name);
|
||||
if (uniform == null) {
|
||||
LOG.warn("Monitor shader {} should have uniform {}, but it was not present.", getName(), name);
|
||||
}
|
||||
|
||||
return uniform;
|
||||
}
|
||||
|
||||
public static void setTerminalData(ByteBuffer buffer, Terminal terminal) {
|
||||
int width = terminal.getWidth(), height = terminal.getHeight();
|
||||
|
||||
var pos = 0;
|
||||
for (var y = 0; y < height; y++) {
|
||||
TextBuffer text = terminal.getLine(y), textColour = terminal.getTextColourLine(y), background = terminal.getBackgroundColourLine(y);
|
||||
for (var x = 0; x < width; x++) {
|
||||
buffer.put(pos, (byte) (text.charAt(x) & 0xFF));
|
||||
buffer.put(pos + 1, (byte) getColour(textColour.charAt(x), Colour.WHITE));
|
||||
buffer.put(pos + 2, (byte) getColour(background.charAt(x), Colour.BLACK));
|
||||
pos += 3;
|
||||
}
|
||||
}
|
||||
|
||||
buffer.limit(pos);
|
||||
}
|
||||
|
||||
public static void setUniformData(ByteBuffer buffer, Terminal terminal) {
|
||||
var pos = 0;
|
||||
var palette = terminal.getPalette();
|
||||
for (var i = 0; i < 16; i++) {
|
||||
{
|
||||
var colour = palette.getColour(i);
|
||||
if (!terminal.isColour()) {
|
||||
var f = FixedWidthFontRenderer.toGreyscale(colour);
|
||||
buffer.putFloat(pos, f).putFloat(pos + 4, f).putFloat(pos + 8, f);
|
||||
} else {
|
||||
buffer.putFloat(pos, (float) colour[0]).putFloat(pos + 4, (float) colour[1]).putFloat(pos + 8, (float) colour[2]);
|
||||
}
|
||||
}
|
||||
|
||||
pos += 4 * 4; // std140 requires these are 4-wide
|
||||
}
|
||||
|
||||
var showCursor = FixedWidthFontRenderer.isCursorVisible(terminal);
|
||||
buffer
|
||||
.putInt(pos, terminal.getWidth()).putInt(pos + 4, terminal.getHeight())
|
||||
.putInt(pos + 8, showCursor ? terminal.getCursorX() : -2)
|
||||
.putInt(pos + 12, showCursor ? terminal.getCursorY() : -2)
|
||||
.putInt(pos + 16, 15 - terminal.getTextColour());
|
||||
|
||||
buffer.limit(UNIFORM_SIZE);
|
||||
}
|
||||
}
|
@@ -4,18 +4,17 @@
|
||||
|
||||
package dan200.computercraft.client.render.text;
|
||||
|
||||
import com.mojang.blaze3d.vertex.ByteBufferBuilder;
|
||||
import com.mojang.blaze3d.vertex.DefaultVertexFormat;
|
||||
import com.mojang.blaze3d.vertex.VertexConsumer;
|
||||
import com.mojang.blaze3d.vertex.VertexFormat;
|
||||
import dan200.computercraft.client.render.RenderTypes;
|
||||
import dan200.computercraft.core.terminal.Palette;
|
||||
import dan200.computercraft.core.terminal.Terminal;
|
||||
import dan200.computercraft.core.terminal.TextBuffer;
|
||||
import dan200.computercraft.core.util.Colour;
|
||||
import net.minecraft.util.FastColor;
|
||||
import net.minecraft.util.ARGB;
|
||||
import org.lwjgl.system.MemoryUtil;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.ByteOrder;
|
||||
|
||||
import static dan200.computercraft.client.render.text.FixedWidthFontRenderer.*;
|
||||
@@ -168,40 +167,38 @@ public final class DirectFixedWidthFontRenderer {
|
||||
public interface QuadEmitter {
|
||||
VertexFormat format();
|
||||
|
||||
ByteBuffer buffer();
|
||||
|
||||
void quad(float x1, float y1, float x2, float y2, float z, int colour, float u1, float v1, float u2, float v2);
|
||||
}
|
||||
|
||||
public record ByteBufferEmitter(ByteBuffer buffer) implements QuadEmitter {
|
||||
public record ByteBufferEmitter(ByteBufferBuilder builder) implements QuadEmitter {
|
||||
@Override
|
||||
public VertexFormat format() {
|
||||
return RenderTypes.TERMINAL.format();
|
||||
return TERMINAL_TEXT.format();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void quad(float x1, float y1, float x2, float y2, float z, int colour, float u1, float v1, float u2, float v2) {
|
||||
DirectFixedWidthFontRenderer.quad(buffer, x1, y1, x2, y2, z, colour, u1, v1, u2, v2);
|
||||
DirectFixedWidthFontRenderer.quad(builder, x1, y1, x2, y2, z, colour, u1, v1, u2, v2);
|
||||
}
|
||||
}
|
||||
|
||||
static void quad(ByteBuffer buffer, float x1, float y1, float x2, float y2, float z, int colour, float u1, float v1, float u2, float v2) {
|
||||
static void quad(ByteBufferBuilder builder, float x1, float y1, float x2, float y2, float z, int colour, float u1, float v1, float u2, float v2) {
|
||||
// Emit a single quad to our buffer. This uses Unsafe (well, LWJGL's MemoryUtil) to directly blit bytes to the
|
||||
// underlying buffer. This allows us to have a single bounds check up-front, rather than one for every write.
|
||||
// This provides significant performance gains, at the cost of well, using Unsafe.
|
||||
// Each vertex is 28 bytes, giving 112 bytes in total. Vertices are of the form (xyz:FFF)(rgba:BBBB)(uv1:FF)(uv2:SS),
|
||||
// Each vertex is 28 bytes, giving 112 bytes in total. Vertices are of the form (xyz:FFF)(abgr:BBBB)(uv1:FF)(uv2:SS),
|
||||
// which matches the POSITION_COLOR_TEX_LIGHTMAP vertex format.
|
||||
|
||||
var position = buffer.position();
|
||||
var addr = MemoryUtil.memAddress(buffer);
|
||||
var addr = builder.reserve(112);
|
||||
|
||||
// We're doing terrible unsafe hacks below, so let's be really sure that what we're doing is reasonable.
|
||||
if (position < 0 || 112 > buffer.limit() - position) throw new IndexOutOfBoundsException();
|
||||
// Require the pointer to be aligned to a 32-bit boundary.
|
||||
if ((addr & 3) != 0) throw new IllegalStateException("Memory is not aligned");
|
||||
if (TERMINAL_TEXT.format().getVertexSize() != 28) {
|
||||
throw new IllegalStateException("Incorrect vertex size");
|
||||
}
|
||||
|
||||
// Pack colour so it is equivalent to rgba:BBBB. This matches the logic in BufferBuilder.
|
||||
var colourAbgr = FastColor.ABGR32.fromArgb32(colour);
|
||||
var colourAbgr = ARGB.toABGR(colour);
|
||||
// Pack colour so it is equivalent to abgr:BBBB. This matches the logic in BufferBuilder.
|
||||
var nativeColour = IS_LITTLE_ENDIAN ? colourAbgr : Integer.reverseBytes(colourAbgr);
|
||||
|
||||
memPutFloat(addr + 0, x1);
|
||||
@@ -240,9 +237,6 @@ public final class DirectFixedWidthFontRenderer {
|
||||
memPutShort(addr + 108, (short) 0xF0);
|
||||
memPutShort(addr + 110, (short) 0xF0);
|
||||
|
||||
// Finally increment the position.
|
||||
buffer.position(position + 112);
|
||||
|
||||
// Well done for getting to the end of this method. I recommend you take a break and go look at cute puppies.
|
||||
}
|
||||
}
|
||||
|
@@ -11,13 +11,13 @@ import dan200.computercraft.core.terminal.Palette;
|
||||
import dan200.computercraft.core.terminal.Terminal;
|
||||
import dan200.computercraft.core.terminal.TextBuffer;
|
||||
import dan200.computercraft.core.util.Colour;
|
||||
import net.minecraft.client.renderer.LightTexture;
|
||||
import net.minecraft.client.renderer.RenderType;
|
||||
import net.minecraft.resources.ResourceLocation;
|
||||
import net.minecraft.util.FastColor;
|
||||
import net.minecraft.util.ARGB;
|
||||
import org.joml.Matrix4f;
|
||||
import org.joml.Vector3f;
|
||||
|
||||
import static dan200.computercraft.client.render.RenderTypes.FULL_BRIGHT_LIGHTMAP;
|
||||
|
||||
/**
|
||||
* Handles rendering fixed width text and computer terminals.
|
||||
* <p>
|
||||
@@ -33,7 +33,12 @@ import static dan200.computercraft.client.render.RenderTypes.FULL_BRIGHT_LIGHTMA
|
||||
* {@link DirectFixedWidthFontRenderer}.
|
||||
*/
|
||||
public final class FixedWidthFontRenderer {
|
||||
public static final ResourceLocation FONT = ResourceLocation.fromNamespaceAndPath("computercraft", "textures/gui/term_font.png");
|
||||
private static final ResourceLocation FONT = ResourceLocation.fromNamespaceAndPath("computercraft", "textures/gui/term_font.png");
|
||||
|
||||
/**
|
||||
* A render type for terminal text.
|
||||
*/
|
||||
public static final RenderType TERMINAL_TEXT = RenderType.text(FONT);
|
||||
|
||||
public static final int FONT_HEIGHT = 9;
|
||||
public static final int FONT_WIDTH = 6;
|
||||
@@ -42,7 +47,7 @@ public final class FixedWidthFontRenderer {
|
||||
static final float BACKGROUND_START = (WIDTH - 6.0f) / WIDTH;
|
||||
static final float BACKGROUND_END = (WIDTH - 4.0f) / WIDTH;
|
||||
|
||||
private static final int BLACK = FastColor.ARGB32.color(255, byteColour(Colour.BLACK.getR()), byteColour(Colour.BLACK.getR()), byteColour(Colour.BLACK.getR()));
|
||||
private static final int BLACK = ARGB.color(255, byteColour(Colour.BLACK.getR()), byteColour(Colour.BLACK.getR()), byteColour(Colour.BLACK.getR()));
|
||||
private static final float Z_OFFSET = 1e-4f;
|
||||
|
||||
private FixedWidthFontRenderer() {
|
||||
@@ -137,7 +142,7 @@ public final class FixedWidthFontRenderer {
|
||||
var rowY = y + FONT_HEIGHT * i;
|
||||
drawString(
|
||||
emitter, x, rowY, terminal.getLine(i), terminal.getTextColourLine(i),
|
||||
palette, FULL_BRIGHT_LIGHTMAP
|
||||
palette, LightTexture.FULL_BRIGHT
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -152,12 +157,12 @@ public final class FixedWidthFontRenderer {
|
||||
// Top and bottom margins
|
||||
drawBackground(
|
||||
emitter, x, y - topMarginSize, terminal.getBackgroundColourLine(0), palette,
|
||||
leftMarginSize, rightMarginSize, topMarginSize, FULL_BRIGHT_LIGHTMAP
|
||||
leftMarginSize, rightMarginSize, topMarginSize, LightTexture.FULL_BRIGHT
|
||||
);
|
||||
|
||||
drawBackground(
|
||||
emitter, x, y + height * FONT_HEIGHT, terminal.getBackgroundColourLine(height - 1), palette,
|
||||
leftMarginSize, rightMarginSize, bottomMarginSize, FULL_BRIGHT_LIGHTMAP
|
||||
leftMarginSize, rightMarginSize, bottomMarginSize, LightTexture.FULL_BRIGHT
|
||||
);
|
||||
|
||||
// The main text
|
||||
@@ -165,7 +170,7 @@ public final class FixedWidthFontRenderer {
|
||||
var rowY = y + FONT_HEIGHT * i;
|
||||
drawBackground(
|
||||
emitter, x, rowY, terminal.getBackgroundColourLine(i), palette,
|
||||
leftMarginSize, rightMarginSize, FONT_HEIGHT, FULL_BRIGHT_LIGHTMAP
|
||||
leftMarginSize, rightMarginSize, FONT_HEIGHT, LightTexture.FULL_BRIGHT
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -181,7 +186,7 @@ public final class FixedWidthFontRenderer {
|
||||
public static void drawCursor(QuadEmitter emitter, float x, float y, Terminal terminal) {
|
||||
if (isCursorVisible(terminal) && FrameInfo.getGlobalCursorBlink()) {
|
||||
var colour = terminal.getPalette().getRenderColours(15 - terminal.getTextColour());
|
||||
drawChar(emitter, x + terminal.getCursorX() * FONT_WIDTH, y + terminal.getCursorY() * FONT_HEIGHT, '_', colour, FULL_BRIGHT_LIGHTMAP);
|
||||
drawChar(emitter, x + terminal.getCursorX() * FONT_WIDTH, y + terminal.getCursorY() * FONT_HEIGHT, '_', colour, LightTexture.FULL_BRIGHT);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -207,7 +212,7 @@ public final class FixedWidthFontRenderer {
|
||||
}
|
||||
|
||||
public static void drawEmptyTerminal(QuadEmitter emitter, float x, float y, float width, float height) {
|
||||
drawQuad(emitter, x, y, 0, width, height, BLACK, FULL_BRIGHT_LIGHTMAP);
|
||||
drawQuad(emitter, x, y, 0, width, height, BLACK, LightTexture.FULL_BRIGHT);
|
||||
}
|
||||
|
||||
public record QuadEmitter(Matrix4f poseMatrix, VertexConsumer consumer) {
|
||||
@@ -220,11 +225,10 @@ public final class FixedWidthFontRenderer {
|
||||
private static void quad(QuadEmitter c, float x1, float y1, float x2, float y2, float z, int colour, float u1, float v1, float u2, float v2, int light) {
|
||||
var poseMatrix = c.poseMatrix();
|
||||
var consumer = c.consumer();
|
||||
int r = FastColor.ARGB32.red(colour), g = FastColor.ARGB32.green(colour), b = FastColor.ARGB32.blue(colour), a = FastColor.ARGB32.alpha(colour);
|
||||
|
||||
consumer.addVertex(poseMatrix, x1, y1, z).setColor(r, g, b, a).setUv(u1, v1).setLight(light);
|
||||
consumer.addVertex(poseMatrix, x1, y2, z).setColor(r, g, b, a).setUv(u1, v2).setLight(light);
|
||||
consumer.addVertex(poseMatrix, x2, y2, z).setColor(r, g, b, a).setUv(u2, v2).setLight(light);
|
||||
consumer.addVertex(poseMatrix, x2, y1, z).setColor(r, g, b, a).setUv(u2, v1).setLight(light);
|
||||
consumer.addVertex(poseMatrix, x1, y1, z).setColor(colour).setUv(u1, v1).setLight(light);
|
||||
consumer.addVertex(poseMatrix, x1, y2, z).setColor(colour).setUv(u1, v2).setLight(light);
|
||||
consumer.addVertex(poseMatrix, x2, y2, z).setColor(colour).setUv(u2, v2).setLight(light);
|
||||
consumer.addVertex(poseMatrix, x2, y1, z).setColor(colour).setUv(u2, v1).setLight(light);
|
||||
}
|
||||
}
|
||||
|
@@ -1,71 +0,0 @@
|
||||
// SPDX-FileCopyrightText: 2022 The CC: Tweaked Developers
|
||||
//
|
||||
// SPDX-License-Identifier: MPL-2.0
|
||||
|
||||
package dan200.computercraft.client.render.vbo;
|
||||
|
||||
import com.mojang.blaze3d.platform.GlStateManager;
|
||||
import com.mojang.blaze3d.systems.RenderSystem;
|
||||
import com.mojang.blaze3d.vertex.BufferUploader;
|
||||
import net.minecraft.Util;
|
||||
import org.lwjgl.opengl.GL;
|
||||
import org.lwjgl.opengl.GL15C;
|
||||
import org.lwjgl.opengl.GL45C;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
|
||||
/**
|
||||
* Provides utilities to interact with OpenGL's buffer objects, either using direct state access or binding/unbinding
|
||||
* it.
|
||||
*/
|
||||
public class DirectBuffers {
|
||||
public static final boolean HAS_DSA;
|
||||
static final boolean ON_LINUX = Util.getPlatform() == Util.OS.LINUX;
|
||||
|
||||
static {
|
||||
var capabilities = GL.getCapabilities();
|
||||
HAS_DSA = capabilities.OpenGL45 || capabilities.GL_ARB_direct_state_access;
|
||||
}
|
||||
|
||||
public static int createBuffer() {
|
||||
return HAS_DSA ? GL45C.glCreateBuffers() : GL15C.glGenBuffers();
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete a previously created buffer.
|
||||
* <p>
|
||||
* On Linux, {@link GlStateManager#_glDeleteBuffers(int)} clears a buffer before deleting it. However, this involves
|
||||
* binding and unbinding the buffer, conflicting with {@link BufferUploader}'s cache. This deletion method uses
|
||||
* our existing {@link #setEmptyBufferData(int, int, int)}, which correctly handles clearing the buffer.
|
||||
*
|
||||
* @param type The buffer's type.
|
||||
* @param id The buffer's ID.
|
||||
*/
|
||||
public static void deleteBuffer(int type, int id) {
|
||||
RenderSystem.assertOnRenderThread();
|
||||
if (ON_LINUX) DirectBuffers.setEmptyBufferData(type, id, GL15C.GL_DYNAMIC_DRAW);
|
||||
GL15C.glDeleteBuffers(id);
|
||||
}
|
||||
|
||||
public static void setBufferData(int type, int id, ByteBuffer buffer, int flags) {
|
||||
if (HAS_DSA) {
|
||||
GL45C.glNamedBufferData(id, buffer, flags);
|
||||
} else {
|
||||
if (type == GL15C.GL_ARRAY_BUFFER) BufferUploader.reset();
|
||||
GlStateManager._glBindBuffer(type, id);
|
||||
GlStateManager._glBufferData(type, buffer, flags);
|
||||
GlStateManager._glBindBuffer(type, 0);
|
||||
}
|
||||
}
|
||||
|
||||
public static void setEmptyBufferData(int type, int id, int flags) {
|
||||
if (HAS_DSA) {
|
||||
GL45C.glNamedBufferData(id, 0, flags);
|
||||
} else {
|
||||
if (type == GL15C.GL_ARRAY_BUFFER) BufferUploader.reset();
|
||||
GlStateManager._glBindBuffer(type, id);
|
||||
GlStateManager._glBufferData(type, 0, flags);
|
||||
GlStateManager._glBindBuffer(type, 0);
|
||||
}
|
||||
}
|
||||
}
|
@@ -1,77 +0,0 @@
|
||||
// SPDX-FileCopyrightText: 2022 The CC: Tweaked Developers
|
||||
//
|
||||
// SPDX-License-Identifier: MPL-2.0
|
||||
|
||||
package dan200.computercraft.client.render.vbo;
|
||||
|
||||
import com.mojang.blaze3d.systems.RenderSystem;
|
||||
import com.mojang.blaze3d.vertex.BufferUploader;
|
||||
import com.mojang.blaze3d.vertex.VertexBuffer;
|
||||
import com.mojang.blaze3d.vertex.VertexFormat;
|
||||
import net.minecraft.client.renderer.ShaderInstance;
|
||||
import org.joml.Matrix4f;
|
||||
import org.lwjgl.opengl.GL15;
|
||||
import org.lwjgl.opengl.GL15C;
|
||||
import org.lwjgl.opengl.GL45C;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
|
||||
/**
|
||||
* A version of {@link VertexBuffer} which allows uploading {@link ByteBuffer}s directly.
|
||||
* <p>
|
||||
* This should probably be its own class (rather than subclassing), but I need access to {@link VertexBuffer#drawWithShader}.
|
||||
*/
|
||||
public class DirectVertexBuffer extends VertexBuffer {
|
||||
private int actualIndexCount;
|
||||
|
||||
public DirectVertexBuffer() {
|
||||
super(Usage.STATIC);
|
||||
if (DirectBuffers.HAS_DSA) {
|
||||
RenderSystem.glDeleteBuffers(vertexBufferId);
|
||||
if (DirectBuffers.ON_LINUX) BufferUploader.reset(); // See comment on DirectBuffers.deleteBuffer.
|
||||
vertexBufferId = GL45C.glCreateBuffers();
|
||||
}
|
||||
}
|
||||
|
||||
public void upload(int vertexCount, VertexFormat.Mode mode, VertexFormat format, ByteBuffer buffer) {
|
||||
bind();
|
||||
|
||||
this.mode = mode;
|
||||
actualIndexCount = indexCount = mode.indexCount(vertexCount);
|
||||
indexType = VertexFormat.IndexType.SHORT;
|
||||
|
||||
RenderSystem.assertOnRenderThread();
|
||||
|
||||
DirectBuffers.setBufferData(GL15.GL_ARRAY_BUFFER, vertexBufferId, buffer, GL15.GL_STATIC_DRAW);
|
||||
if (format != this.format) {
|
||||
if (this.format != null) this.format.clearBufferState();
|
||||
this.format = format;
|
||||
|
||||
GL15C.glBindBuffer(GL15C.GL_ARRAY_BUFFER, vertexBufferId);
|
||||
format.setupBufferState();
|
||||
GL15C.glBindBuffer(GL15C.GL_ARRAY_BUFFER, 0);
|
||||
}
|
||||
|
||||
var indexBuffer = RenderSystem.getSequentialBuffer(mode);
|
||||
if (indexBuffer != sequentialIndices || !indexBuffer.hasStorage(indexCount)) {
|
||||
indexBuffer.bind(indexCount);
|
||||
sequentialIndices = indexBuffer;
|
||||
}
|
||||
}
|
||||
|
||||
public void drawWithShader(Matrix4f modelView, Matrix4f projection, ShaderInstance shader, int indexCount) {
|
||||
this.indexCount = indexCount;
|
||||
drawWithShader(modelView, projection, shader);
|
||||
this.indexCount = actualIndexCount;
|
||||
}
|
||||
|
||||
public int getIndexCount() {
|
||||
return actualIndexCount;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
super.close();
|
||||
if (DirectBuffers.ON_LINUX) BufferUploader.reset(); // See comment on DirectBuffers.deleteBuffer.
|
||||
}
|
||||
}
|
@@ -5,7 +5,6 @@
|
||||
package dan200.computercraft.client.turtle;
|
||||
|
||||
import dan200.computercraft.api.ComputerCraftAPI;
|
||||
import dan200.computercraft.api.client.ModelLocation;
|
||||
import dan200.computercraft.api.client.TransformedModel;
|
||||
import dan200.computercraft.api.client.turtle.TurtleUpgradeModeller;
|
||||
import dan200.computercraft.api.turtle.ITurtleAccess;
|
||||
@@ -39,23 +38,23 @@ public class TurtleModemModeller implements TurtleUpgradeModeller<TurtleModem> {
|
||||
}
|
||||
|
||||
private record ModemModels(
|
||||
ModelLocation leftOffModel, ModelLocation rightOffModel,
|
||||
ModelLocation leftOnModel, ModelLocation rightOnModel
|
||||
ResourceLocation leftOffModel, ResourceLocation rightOffModel,
|
||||
ResourceLocation leftOnModel, ResourceLocation rightOnModel
|
||||
) {
|
||||
private static final ModemModels NORMAL = create("normal");
|
||||
private static final ModemModels ADVANCED = create("advanced");
|
||||
|
||||
public static ModemModels create(String type) {
|
||||
return new ModemModels(
|
||||
ModelLocation.ofResource(ResourceLocation.fromNamespaceAndPath(ComputerCraftAPI.MOD_ID, "block/turtle_modem_" + type + "_off_left")),
|
||||
ModelLocation.ofResource(ResourceLocation.fromNamespaceAndPath(ComputerCraftAPI.MOD_ID, "block/turtle_modem_" + type + "_off_right")),
|
||||
ModelLocation.ofResource(ResourceLocation.fromNamespaceAndPath(ComputerCraftAPI.MOD_ID, "block/turtle_modem_" + type + "_on_left")),
|
||||
ModelLocation.ofResource(ResourceLocation.fromNamespaceAndPath(ComputerCraftAPI.MOD_ID, "block/turtle_modem_" + type + "_on_right"))
|
||||
ResourceLocation.fromNamespaceAndPath(ComputerCraftAPI.MOD_ID, "block/turtle_modem_" + type + "_off_left"),
|
||||
ResourceLocation.fromNamespaceAndPath(ComputerCraftAPI.MOD_ID, "block/turtle_modem_" + type + "_off_right"),
|
||||
ResourceLocation.fromNamespaceAndPath(ComputerCraftAPI.MOD_ID, "block/turtle_modem_" + type + "_on_left"),
|
||||
ResourceLocation.fromNamespaceAndPath(ComputerCraftAPI.MOD_ID, "block/turtle_modem_" + type + "_on_right")
|
||||
);
|
||||
}
|
||||
|
||||
public Stream<ResourceLocation> getDependencies() {
|
||||
return Stream.of(leftOffModel, rightOffModel, leftOnModel, rightOnModel).flatMap(ModelLocation::getDependencies);
|
||||
return Stream.of(leftOffModel, rightOffModel, leftOnModel, rightOnModel);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -4,7 +4,6 @@
|
||||
|
||||
package dan200.computercraft.client.turtle;
|
||||
|
||||
import com.mojang.math.Transformation;
|
||||
import dan200.computercraft.api.client.TransformedModel;
|
||||
import dan200.computercraft.api.client.turtle.TurtleUpgradeModeller;
|
||||
import dan200.computercraft.api.turtle.ITurtleAccess;
|
||||
@@ -25,7 +24,7 @@ import java.util.stream.Stream;
|
||||
*/
|
||||
public final class TurtleUpgradeModellers {
|
||||
private static final TurtleUpgradeModeller<ITurtleUpgrade> NULL_TURTLE_MODELLER = (upgrade, turtle, side, data) ->
|
||||
new TransformedModel(Minecraft.getInstance().getModelManager().getMissingModel(), Transformation.identity());
|
||||
TransformedModel.of(Minecraft.getInstance().getModelManager().getMissingModel());
|
||||
|
||||
private static final Map<UpgradeType<? extends ITurtleUpgrade>, TurtleUpgradeModeller<?>> turtleModels = new ConcurrentHashMap<>();
|
||||
private static volatile boolean fetchedModels;
|
||||
|
@@ -10,10 +10,14 @@ 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.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;
|
||||
import net.minecraft.client.data.models.ItemModelGenerators;
|
||||
import net.minecraft.client.renderer.texture.atlas.SpriteSource;
|
||||
import net.minecraft.client.renderer.texture.atlas.SpriteSources;
|
||||
import net.minecraft.client.renderer.texture.atlas.sources.SingleFile;
|
||||
@@ -24,7 +28,6 @@ import net.minecraft.data.PackOutput;
|
||||
import net.minecraft.data.registries.RegistryPatchGenerator;
|
||||
import net.minecraft.data.tags.TagsProvider;
|
||||
import net.minecraft.resources.ResourceLocation;
|
||||
import net.minecraft.server.packs.PackType;
|
||||
import net.minecraft.world.item.Item;
|
||||
import net.minecraft.world.level.block.Block;
|
||||
|
||||
@@ -57,25 +60,27 @@ public final class DataProviders {
|
||||
var fullRegistries = fullRegistryPatch.thenApply(RegistrySetBuilder.PatchedRegistries::full);
|
||||
|
||||
generator.registries(fullRegistryPatch);
|
||||
generator.add(out -> new RecipeProvider(out, fullRegistries));
|
||||
generator.add(out -> new RecipeProvider.Runner(out, fullRegistries));
|
||||
|
||||
var blockTags = generator.blockTags(TagProvider::blockTags);
|
||||
generator.itemTags(TagProvider::itemTags, blockTags);
|
||||
|
||||
generator.add(out -> new net.minecraft.data.loot.LootTableProvider(out, Set.of(), LootTableProvider.getTables(), fullRegistries));
|
||||
|
||||
generator.add(out -> new ModelProvider(out, BlockModelProvider::addBlockModels, ItemModelProvider::addItemModels));
|
||||
|
||||
generator.add(out -> new LanguageProvider(out, fullRegistries));
|
||||
|
||||
generator.addFromCodec("Block atlases", PackType.CLIENT_RESOURCES, "atlases", SpriteSources.FILE_CODEC, out -> {
|
||||
generator.addFromCodec("Block atlases", PackOutput.Target.RESOURCE_PACK, "atlases", SpriteSources.FILE_CODEC, out -> {
|
||||
out.accept(ResourceLocation.withDefaultNamespace("blocks"), makeSprites(Stream.of(
|
||||
UpgradeSlot.LEFT_UPGRADE,
|
||||
UpgradeSlot.RIGHT_UPGRADE,
|
||||
LecternPrintoutModel.TEXTURE,
|
||||
LecternPocketModel.TEXTURE_NORMAL, LecternPocketModel.TEXTURE_ADVANCED,
|
||||
LecternPocketModel.TEXTURE_COLOUR, LecternPocketModel.TEXTURE_FRAME, LecternPocketModel.TEXTURE_LIGHT
|
||||
)));
|
||||
|
||||
out.accept(ResourceLocation.withDefaultNamespace("gui"), makeSprites(Stream.of(
|
||||
UpgradeSlot.LEFT_UPGRADE,
|
||||
UpgradeSlot.RIGHT_UPGRADE
|
||||
)));
|
||||
|
||||
out.accept(GuiSprites.SPRITE_SHEET, makeSprites(
|
||||
// Computers
|
||||
GuiSprites.COMPUTER_NORMAL.textures(),
|
||||
@@ -91,6 +96,8 @@ public final class DataProviders {
|
||||
return registries.lookupOrThrow(TurtleOverlay.REGISTRY).listElements().map(x -> x.value().model());
|
||||
}
|
||||
});
|
||||
|
||||
generator.addModels(BlockModelProvider::addBlockModels, ItemModelProvider::addItemModels);
|
||||
}
|
||||
|
||||
@SafeVarargs
|
||||
@@ -104,7 +111,7 @@ public final class DataProviders {
|
||||
|
||||
<T extends DataProvider> T add(DataProvider.Factory<T> factory);
|
||||
|
||||
<T> void addFromCodec(String name, PackType type, String directory, Codec<T> codec, Consumer<BiConsumer<ResourceLocation, T>> output);
|
||||
<T> void addFromCodec(String name, PackOutput.Target target, String directory, Codec<T> codec, Consumer<BiConsumer<ResourceLocation, T>> output);
|
||||
|
||||
TagsProvider<Block> blockTags(Consumer<TagProvider.TagConsumer<Block>> tags);
|
||||
|
||||
@@ -116,5 +123,14 @@ public final class DataProviders {
|
||||
* @param registries The patched registries to write.
|
||||
*/
|
||||
void registries(CompletableFuture<RegistrySetBuilder.PatchedRegistries> registries);
|
||||
|
||||
/**
|
||||
* Generate block and item models.
|
||||
*
|
||||
* @param blocks The generator for block states and models.
|
||||
* @param items The generator for item models.
|
||||
* @see net.minecraft.client.data.models.ModelProvider
|
||||
*/
|
||||
void addModels(Consumer<BlockModelGenerators> blocks, Consumer<ItemModelGenerators> items);
|
||||
}
|
||||
}
|
||||
|
@@ -1,97 +0,0 @@
|
||||
// SPDX-FileCopyrightText: 2022 The CC: Tweaked Developers
|
||||
//
|
||||
// SPDX-License-Identifier: MPL-2.0
|
||||
|
||||
package dan200.computercraft.data;
|
||||
|
||||
import dan200.computercraft.api.ComputerCraftAPI;
|
||||
import dan200.computercraft.shared.ModRegistry;
|
||||
import net.minecraft.data.models.ItemModelGenerators;
|
||||
import net.minecraft.data.models.model.ModelTemplate;
|
||||
import net.minecraft.data.models.model.ModelTemplates;
|
||||
import net.minecraft.data.models.model.TextureMapping;
|
||||
import net.minecraft.data.models.model.TextureSlot;
|
||||
import net.minecraft.resources.ResourceLocation;
|
||||
import net.minecraft.world.item.Item;
|
||||
|
||||
import java.util.Optional;
|
||||
|
||||
import static net.minecraft.data.models.model.ModelLocationUtils.getModelLocation;
|
||||
|
||||
public final class ItemModelProvider {
|
||||
private ItemModelProvider() {
|
||||
}
|
||||
|
||||
public static void addItemModels(ItemModelGenerators generators) {
|
||||
registerDisk(generators, ModRegistry.Items.DISK.get());
|
||||
registerDisk(generators, ModRegistry.Items.TREASURE_DISK.get());
|
||||
|
||||
registerPocketComputer(generators, getModelLocation(ModRegistry.Items.POCKET_COMPUTER_NORMAL.get()), false);
|
||||
registerPocketComputer(generators, getModelLocation(ModRegistry.Items.POCKET_COMPUTER_ADVANCED.get()), false);
|
||||
registerPocketComputer(generators, ResourceLocation.fromNamespaceAndPath(ComputerCraftAPI.MOD_ID, "item/pocket_computer_colour"), true);
|
||||
|
||||
generators.generateFlatItem(ModRegistry.Items.PRINTED_BOOK.get(), ModelTemplates.FLAT_ITEM);
|
||||
generators.generateFlatItem(ModRegistry.Items.PRINTED_PAGE.get(), ModelTemplates.FLAT_ITEM);
|
||||
generators.generateFlatItem(ModRegistry.Items.PRINTED_PAGES.get(), ModelTemplates.FLAT_ITEM);
|
||||
}
|
||||
|
||||
private static void registerPocketComputer(ItemModelGenerators generators, ResourceLocation id, boolean off) {
|
||||
createFlatItem(generators, id.withSuffix("_blinking"),
|
||||
ResourceLocation.fromNamespaceAndPath(ComputerCraftAPI.MOD_ID, "item/pocket_computer_blink"),
|
||||
id,
|
||||
ResourceLocation.fromNamespaceAndPath(ComputerCraftAPI.MOD_ID, "item/pocket_computer_light")
|
||||
);
|
||||
|
||||
createFlatItem(generators, id.withSuffix("_on"),
|
||||
ResourceLocation.fromNamespaceAndPath(ComputerCraftAPI.MOD_ID, "item/pocket_computer_on"),
|
||||
id,
|
||||
ResourceLocation.fromNamespaceAndPath(ComputerCraftAPI.MOD_ID, "item/pocket_computer_light")
|
||||
);
|
||||
|
||||
// Don't emit the default/off state for advanced/normal pocket computers, as they have item overrides.
|
||||
if (off) {
|
||||
createFlatItem(generators, id,
|
||||
ResourceLocation.fromNamespaceAndPath(ComputerCraftAPI.MOD_ID, "item/pocket_computer_frame"),
|
||||
id
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
private static void registerDisk(ItemModelGenerators generators, Item item) {
|
||||
createFlatItem(generators, item,
|
||||
ResourceLocation.fromNamespaceAndPath(ComputerCraftAPI.MOD_ID, "item/disk_frame"),
|
||||
ResourceLocation.fromNamespaceAndPath(ComputerCraftAPI.MOD_ID, "item/disk_colour")
|
||||
);
|
||||
}
|
||||
|
||||
private static void createFlatItem(ItemModelGenerators generators, Item item, ResourceLocation... ids) {
|
||||
createFlatItem(generators, getModelLocation(item), ids);
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate a flat item from an arbitrary number of layers.
|
||||
*
|
||||
* @param generators The current item generator helper.
|
||||
* @param model The model we're writing to.
|
||||
* @param textures The textures which make up this model.
|
||||
* @see net.minecraft.client.renderer.block.model.ItemModelGenerator The parser for this file format.
|
||||
*/
|
||||
private static void createFlatItem(ItemModelGenerators generators, ResourceLocation model, ResourceLocation... textures) {
|
||||
if (textures.length > 5) throw new IndexOutOfBoundsException("Too many layers");
|
||||
if (textures.length == 0) throw new IndexOutOfBoundsException("Must have at least one texture");
|
||||
if (textures.length == 1) {
|
||||
ModelTemplates.FLAT_ITEM.create(model, TextureMapping.layer0(textures[0]), generators.output);
|
||||
return;
|
||||
}
|
||||
|
||||
var slots = new TextureSlot[textures.length];
|
||||
var mapping = new TextureMapping();
|
||||
for (var i = 0; i < textures.length; i++) {
|
||||
var slot = slots[i] = TextureSlot.create("layer" + i);
|
||||
mapping.put(slot, textures[i]);
|
||||
}
|
||||
|
||||
new ModelTemplate(Optional.of(ResourceLocation.withDefaultNamespace("item/generated")), Optional.empty(), slots)
|
||||
.create(model, mapping, generators.output);
|
||||
}
|
||||
}
|
@@ -278,19 +278,18 @@ public final class LanguageProvider implements DataProvider {
|
||||
addConfigEntry(ConfigSpec.monitorWidth, "Max monitor width");
|
||||
addConfigEntry(ConfigSpec.monitorHeight, "Max monitor height");
|
||||
|
||||
addConfigEntry(ConfigSpec.monitorRenderer, "Monitor renderer");
|
||||
addConfigEntry(ConfigSpec.monitorDistance, "Monitor distance");
|
||||
addConfigEntry(ConfigSpec.uploadNagDelay, "Upload nag delay");
|
||||
}
|
||||
|
||||
private Stream<String> getExpectedKeys(HolderLookup.Provider registries) {
|
||||
return Stream.of(
|
||||
BuiltInRegistries.BLOCK.holders()
|
||||
BuiltInRegistries.BLOCK.listElements()
|
||||
.filter(x -> x.key().location().getNamespace().equals(ComputerCraftAPI.MOD_ID))
|
||||
.map(x -> x.value().getDescriptionId())
|
||||
// Exclude blocks that just reuse vanilla translations, such as the lectern.
|
||||
.filter(x -> !x.startsWith("block.minecraft.")),
|
||||
BuiltInRegistries.ITEM.holders()
|
||||
BuiltInRegistries.ITEM.listElements()
|
||||
.filter(x -> x.key().location().getNamespace().equals(ComputerCraftAPI.MOD_ID))
|
||||
.map(x -> x.value().getDescriptionId()),
|
||||
registries.lookupOrThrow(ITurtleUpgrade.REGISTRY).listElements().flatMap(x -> getTranslationKeys(x.value().getAdjective())),
|
||||
|
@@ -60,7 +60,7 @@ class LootTableProvider {
|
||||
|
||||
blockDrop(add, ModRegistry.Blocks.LECTERN, LootItem.lootTableItem(Items.LECTERN), ExplosionCondition.survivesExplosion());
|
||||
|
||||
add.accept(ModRegistry.Blocks.CABLE.get().getLootTable(), LootTable
|
||||
add.accept(ModRegistry.Blocks.CABLE.get().getLootTable().orElseThrow(), LootTable
|
||||
.lootTable()
|
||||
.withPool(LootPool.lootPool()
|
||||
.setRolls(ConstantValue.exactly(1))
|
||||
@@ -115,7 +115,7 @@ class LootTableProvider {
|
||||
LootItemCondition.Builder condition
|
||||
) {
|
||||
var block = wrapper.get();
|
||||
add.accept(block.getLootTable(), LootTable
|
||||
add.accept(block.getLootTable().orElseThrow(), LootTable
|
||||
.lootTable()
|
||||
.withPool(LootPool.lootPool()
|
||||
.setRolls(ConstantValue.exactly(1))
|
||||
|
@@ -1,101 +0,0 @@
|
||||
// SPDX-FileCopyrightText: 2022 The CC: Tweaked Developers
|
||||
//
|
||||
// SPDX-License-Identifier: MPL-2.0
|
||||
|
||||
package dan200.computercraft.data;
|
||||
|
||||
import com.google.gson.JsonElement;
|
||||
import dan200.computercraft.shared.util.RegistryHelper;
|
||||
import net.minecraft.Util;
|
||||
import net.minecraft.core.registries.BuiltInRegistries;
|
||||
import net.minecraft.data.CachedOutput;
|
||||
import net.minecraft.data.DataProvider;
|
||||
import net.minecraft.data.PackOutput;
|
||||
import net.minecraft.data.models.BlockModelGenerators;
|
||||
import net.minecraft.data.models.ItemModelGenerators;
|
||||
import net.minecraft.data.models.blockstates.BlockStateGenerator;
|
||||
import net.minecraft.data.models.model.DelegatedModel;
|
||||
import net.minecraft.data.models.model.ModelLocationUtils;
|
||||
import net.minecraft.resources.ResourceLocation;
|
||||
import net.minecraft.world.item.Item;
|
||||
import net.minecraft.world.level.block.Block;
|
||||
|
||||
import java.nio.file.Path;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.function.BiConsumer;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.function.Function;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
/**
|
||||
* A copy of {@link net.minecraft.data.models.ModelProvider} which accepts a custom generator.
|
||||
* <p>
|
||||
* Please don't sue me Mojang. Or at least make these changes to vanilla before doing so!
|
||||
*/
|
||||
public class ModelProvider implements DataProvider {
|
||||
private final PackOutput.PathProvider blockStatePath;
|
||||
private final PackOutput.PathProvider modelPath;
|
||||
|
||||
private final Consumer<BlockModelGenerators> blocks;
|
||||
private final Consumer<ItemModelGenerators> items;
|
||||
|
||||
public ModelProvider(PackOutput output, Consumer<BlockModelGenerators> blocks, Consumer<ItemModelGenerators> items) {
|
||||
blockStatePath = output.createPathProvider(PackOutput.Target.RESOURCE_PACK, "blockstates");
|
||||
modelPath = output.createPathProvider(PackOutput.Target.RESOURCE_PACK, "models");
|
||||
|
||||
this.blocks = blocks;
|
||||
this.items = items;
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompletableFuture<?> run(CachedOutput output) {
|
||||
Map<Block, BlockStateGenerator> blockStates = new HashMap<>();
|
||||
Consumer<BlockStateGenerator> addBlockState = generator -> {
|
||||
var block = generator.getBlock();
|
||||
if (blockStates.containsKey(block)) {
|
||||
throw new IllegalStateException("Duplicate blockstate definition for " + block);
|
||||
}
|
||||
blockStates.put(block, generator);
|
||||
};
|
||||
|
||||
Map<ResourceLocation, Supplier<JsonElement>> models = new HashMap<>();
|
||||
BiConsumer<ResourceLocation, Supplier<JsonElement>> addModel = (id, contents) -> {
|
||||
if (models.containsKey(id)) throw new IllegalStateException("Duplicate model definition for " + id);
|
||||
models.put(id, contents);
|
||||
};
|
||||
Set<Item> explicitItems = new HashSet<>();
|
||||
blocks.accept(new BlockModelGenerators(addBlockState, addModel, explicitItems::add));
|
||||
items.accept(new ItemModelGenerators(addModel));
|
||||
|
||||
for (var block : BuiltInRegistries.BLOCK) {
|
||||
if (!blockStates.containsKey(block)) continue;
|
||||
|
||||
var item = Item.BY_BLOCK.get(block);
|
||||
if (item == null || explicitItems.contains(item)) continue;
|
||||
|
||||
var model = ModelLocationUtils.getModelLocation(item);
|
||||
if (!models.containsKey(model)) {
|
||||
models.put(model, new DelegatedModel(ModelLocationUtils.getModelLocation(block)));
|
||||
}
|
||||
}
|
||||
|
||||
List<CompletableFuture<?>> futures = new ArrayList<>();
|
||||
saveCollection(output, futures, blockStates, x -> blockStatePath.json(RegistryHelper.getKeyOrThrow(BuiltInRegistries.BLOCK, x)));
|
||||
saveCollection(output, futures, models, modelPath::json);
|
||||
return Util.sequenceFailFast(futures);
|
||||
}
|
||||
|
||||
private <T> void saveCollection(CachedOutput output, List<CompletableFuture<?>> futures, Map<T, ? extends Supplier<JsonElement>> items, Function<T, Path> getLocation) {
|
||||
for (Map.Entry<T, ? extends Supplier<JsonElement>> entry : items.entrySet()) {
|
||||
var path = getLocation.apply(entry.getKey());
|
||||
|
||||
futures.add(DataProvider.saveStable(output, entry.getValue().get(), path));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return "Block State Definitions";
|
||||
}
|
||||
}
|
@@ -4,14 +4,11 @@
|
||||
|
||||
package dan200.computercraft.data;
|
||||
|
||||
import com.google.gson.JsonObject;
|
||||
import com.mojang.authlib.GameProfile;
|
||||
import com.mojang.serialization.JsonOps;
|
||||
import dan200.computercraft.api.ComputerCraftAPI;
|
||||
import dan200.computercraft.api.pocket.IPocketUpgrade;
|
||||
import dan200.computercraft.api.turtle.ITurtleUpgrade;
|
||||
import dan200.computercraft.api.upgrades.UpgradeData;
|
||||
import dan200.computercraft.core.util.Colour;
|
||||
import dan200.computercraft.data.recipe.ShapedSpecBuilder;
|
||||
import dan200.computercraft.data.recipe.ShapelessSpecBuilder;
|
||||
import dan200.computercraft.shared.ModRegistry;
|
||||
@@ -24,7 +21,6 @@ import dan200.computercraft.shared.platform.RecipeIngredients;
|
||||
import dan200.computercraft.shared.pocket.items.PocketComputerItem;
|
||||
import dan200.computercraft.shared.pocket.recipes.PocketComputerUpgradeRecipe;
|
||||
import dan200.computercraft.shared.recipe.ImpostorShapedRecipe;
|
||||
import dan200.computercraft.shared.recipe.ImpostorShapelessRecipe;
|
||||
import dan200.computercraft.shared.recipe.TransformShapedRecipe;
|
||||
import dan200.computercraft.shared.recipe.TransformShapelessRecipe;
|
||||
import dan200.computercraft.shared.recipe.function.CopyComponents;
|
||||
@@ -37,6 +33,7 @@ import dan200.computercraft.shared.util.RegistryHelper;
|
||||
import net.minecraft.advancements.Criterion;
|
||||
import net.minecraft.advancements.critereon.InventoryChangeTrigger;
|
||||
import net.minecraft.advancements.critereon.ItemPredicate;
|
||||
import net.minecraft.core.HolderGetter;
|
||||
import net.minecraft.core.HolderLookup;
|
||||
import net.minecraft.core.component.DataComponents;
|
||||
import net.minecraft.core.registries.BuiltInRegistries;
|
||||
@@ -44,15 +41,14 @@ import net.minecraft.core.registries.Registries;
|
||||
import net.minecraft.data.PackOutput;
|
||||
import net.minecraft.data.recipes.RecipeCategory;
|
||||
import net.minecraft.data.recipes.RecipeOutput;
|
||||
import net.minecraft.data.recipes.ShapedRecipeBuilder;
|
||||
import net.minecraft.data.recipes.ShapelessRecipeBuilder;
|
||||
import net.minecraft.resources.ResourceKey;
|
||||
import net.minecraft.resources.ResourceLocation;
|
||||
import net.minecraft.tags.ItemTags;
|
||||
import net.minecraft.tags.TagKey;
|
||||
import net.minecraft.util.GsonHelper;
|
||||
import net.minecraft.world.item.*;
|
||||
import net.minecraft.world.item.component.DyedItemColor;
|
||||
import net.minecraft.world.item.DyeColor;
|
||||
import net.minecraft.world.item.Item;
|
||||
import net.minecraft.world.item.ItemStack;
|
||||
import net.minecraft.world.item.Items;
|
||||
import net.minecraft.world.item.component.ResolvableProfile;
|
||||
import net.minecraft.world.item.crafting.CraftingBookCategory;
|
||||
import net.minecraft.world.item.crafting.Ingredient;
|
||||
@@ -62,68 +58,46 @@ import net.minecraft.world.level.ItemLike;
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
import static dan200.computercraft.api.ComputerCraftTags.Items.COMPUTER;
|
||||
import static dan200.computercraft.api.ComputerCraftTags.Items.WIRED_MODEM;
|
||||
|
||||
final class RecipeProvider extends net.minecraft.data.recipes.RecipeProvider {
|
||||
private final RecipeIngredients ingredients = PlatformHelper.get().getRecipeIngredients();
|
||||
private final RecipeIngredients ingredients;
|
||||
private final HolderGetter<Item> items;
|
||||
|
||||
private final CompletableFuture<HolderLookup.Provider> registries;
|
||||
|
||||
RecipeProvider(PackOutput output, CompletableFuture<HolderLookup.Provider> registries) {
|
||||
super(output, registries);
|
||||
this.registries = registries;
|
||||
}
|
||||
|
||||
private HolderLookup.Provider registries() {
|
||||
try {
|
||||
return registries.get();
|
||||
} catch (InterruptedException e) {
|
||||
Thread.currentThread().interrupt();
|
||||
throw new RuntimeException("Interrupted");
|
||||
} catch (ExecutionException e) {
|
||||
var cause = e.getCause();
|
||||
throw cause instanceof RuntimeException rt ? rt : new RuntimeException("Unexpected error", cause);
|
||||
}
|
||||
RecipeProvider(HolderLookup.Provider registries, RecipeOutput recipeOutput) {
|
||||
super(registries, recipeOutput);
|
||||
this.items = registries.lookupOrThrow(Registries.ITEM);
|
||||
ingredients = PlatformHelper.get().getRecipeIngredients();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void buildRecipes(RecipeOutput add) {
|
||||
var registries = registries();
|
||||
public void buildRecipes() {
|
||||
basicRecipes();
|
||||
diskColours();
|
||||
pocketUpgrades();
|
||||
turtleUpgrades();
|
||||
turtleOverlays();
|
||||
|
||||
basicRecipes(add);
|
||||
diskColours(add);
|
||||
pocketUpgrades(add, registries);
|
||||
turtleUpgrades(add, registries);
|
||||
turtleOverlays(add, registries);
|
||||
|
||||
addSpecial(add, new DiskRecipe(CraftingBookCategory.MISC));
|
||||
addSpecial(add, new ColourableRecipe(CraftingBookCategory.MISC));
|
||||
addSpecial(add, new ClearColourRecipe(CraftingBookCategory.MISC));
|
||||
addSpecial(add, new TurtleUpgradeRecipe(CraftingBookCategory.MISC));
|
||||
addSpecial(add, new PocketComputerUpgradeRecipe(CraftingBookCategory.MISC));
|
||||
special(new ColourableRecipe(CraftingBookCategory.MISC));
|
||||
special(new ClearColourRecipe(CraftingBookCategory.MISC));
|
||||
special(new TurtleUpgradeRecipe(CraftingBookCategory.MISC));
|
||||
special(new PocketComputerUpgradeRecipe(CraftingBookCategory.MISC));
|
||||
}
|
||||
|
||||
/**
|
||||
* Register a crafting recipe for a disk of every dye colour.
|
||||
*
|
||||
* @param output The callback to add recipes.
|
||||
* Register a disk recipe.
|
||||
*/
|
||||
private void diskColours(RecipeOutput output) {
|
||||
for (var colour : Colour.VALUES) {
|
||||
ShapelessSpecBuilder
|
||||
.shapeless(RecipeCategory.REDSTONE, DataComponentUtil.createStack(ModRegistry.Items.DISK.get(), DataComponents.DYED_COLOR, new DyedItemColor(colour.getHex(), false)))
|
||||
.requires(ingredients.redstone())
|
||||
.requires(Items.PAPER)
|
||||
.requires(DyeItem.byColor(ofColour(colour)))
|
||||
.group("computercraft:disk")
|
||||
.unlockedBy("has_drive", inventoryChange(ModRegistry.Items.DISK_DRIVE.get()))
|
||||
.build(ImpostorShapelessRecipe::new)
|
||||
.save(output, ResourceLocation.fromNamespaceAndPath(ComputerCraftAPI.MOD_ID, "disk_" + (colour.ordinal() + 1)));
|
||||
}
|
||||
private void diskColours() {
|
||||
customShapeless(RecipeCategory.REDSTONE, ModRegistry.Items.DISK.get())
|
||||
.requires(ingredients.redstone())
|
||||
.requires(Items.PAPER)
|
||||
.group("computercraft:disk")
|
||||
.unlockedBy("has_drive", has(ModRegistry.Items.DISK_DRIVE.get()))
|
||||
.build(d -> new DiskRecipe(d.properties(), d.ingredients()))
|
||||
.save(output, ResourceLocation.fromNamespaceAndPath(ComputerCraftAPI.MOD_ID, "disk"));
|
||||
}
|
||||
|
||||
private static List<TurtleItem> turtleItems() {
|
||||
@@ -132,26 +106,22 @@ final class RecipeProvider extends net.minecraft.data.recipes.RecipeProvider {
|
||||
|
||||
/**
|
||||
* Register a crafting recipe for each turtle upgrade.
|
||||
*
|
||||
* @param add The callback to add recipes.
|
||||
* @param registries The currently available registries.
|
||||
*/
|
||||
private void turtleUpgrades(RecipeOutput add, HolderLookup.Provider registries) {
|
||||
private void turtleUpgrades() {
|
||||
for (var turtleItem : turtleItems()) {
|
||||
var name = RegistryHelper.getKeyOrThrow(BuiltInRegistries.ITEM, turtleItem);
|
||||
|
||||
registries.lookupOrThrow(ITurtleUpgrade.REGISTRY).listElements().forEach(upgradeHolder -> {
|
||||
var upgrade = upgradeHolder.value();
|
||||
ShapedSpecBuilder
|
||||
.shaped(RecipeCategory.REDSTONE, DataComponentUtil.createStack(turtleItem, ModRegistry.DataComponents.RIGHT_TURTLE_UPGRADE.get(), UpgradeData.ofDefault(upgradeHolder)))
|
||||
customShaped(RecipeCategory.REDSTONE, DataComponentUtil.createStack(turtleItem, ModRegistry.DataComponents.RIGHT_TURTLE_UPGRADE.get(), UpgradeData.ofDefault(upgradeHolder)))
|
||||
.group(name.toString())
|
||||
.pattern("#T")
|
||||
.define('T', turtleItem)
|
||||
.define('#', upgrade.getCraftingItem().getItem())
|
||||
.unlockedBy("has_items", inventoryChange(turtleItem, upgrade.getCraftingItem().getItem()))
|
||||
.unlockedBy("has_items", has(turtleItem, upgrade.getCraftingItem().getItem()))
|
||||
.build(ImpostorShapedRecipe::new)
|
||||
.save(
|
||||
add,
|
||||
output,
|
||||
name.withSuffix(String.format("/%s/%s", upgradeHolder.key().location().getNamespace(), upgradeHolder.key().location().getPath()))
|
||||
);
|
||||
});
|
||||
@@ -164,44 +134,40 @@ final class RecipeProvider extends net.minecraft.data.recipes.RecipeProvider {
|
||||
|
||||
/**
|
||||
* Register a crafting recipe for each pocket upgrade.
|
||||
*
|
||||
* @param add The callback to add recipes.
|
||||
* @param registries The currently available registries.
|
||||
*/
|
||||
private void pocketUpgrades(RecipeOutput add, HolderLookup.Provider registries) {
|
||||
private void pocketUpgrades() {
|
||||
for (var pocket : pocketComputerItems()) {
|
||||
var name = RegistryHelper.getKeyOrThrow(BuiltInRegistries.ITEM, pocket).withPath(x -> x.replace("pocket_computer_", "pocket_"));
|
||||
|
||||
registries.lookupOrThrow(IPocketUpgrade.REGISTRY).listElements().forEach(upgradeHolder -> {
|
||||
var upgrade = upgradeHolder.value();
|
||||
ShapedSpecBuilder
|
||||
.shaped(RecipeCategory.REDSTONE, DataComponentUtil.createStack(pocket, ModRegistry.DataComponents.POCKET_UPGRADE.get(), UpgradeData.ofDefault(upgradeHolder)))
|
||||
customShaped(RecipeCategory.REDSTONE, DataComponentUtil.createStack(pocket, ModRegistry.DataComponents.POCKET_UPGRADE.get(), UpgradeData.ofDefault(upgradeHolder)))
|
||||
.group(name.toString())
|
||||
.pattern("#")
|
||||
.pattern("P")
|
||||
.define('P', pocket)
|
||||
.define('#', upgrade.getCraftingItem().getItem())
|
||||
.unlockedBy("has_items", inventoryChange(pocket, upgrade.getCraftingItem().getItem()))
|
||||
.unlockedBy("has_items", has(pocket, upgrade.getCraftingItem().getItem()))
|
||||
.build(ImpostorShapedRecipe::new)
|
||||
.save(
|
||||
add,
|
||||
output,
|
||||
name.withSuffix(String.format("/%s/%s", upgradeHolder.key().location().getNamespace(), upgradeHolder.key().location().getPath()))
|
||||
);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private void turtleOverlays(RecipeOutput add, HolderLookup.Provider registries) {
|
||||
turtleOverlay(add, registries, TurtleOverlays.TRANS_FLAG, x -> x
|
||||
.unlockedBy("has_dye", inventoryChange(itemPredicate(ingredients.dye())))
|
||||
private void turtleOverlays() {
|
||||
turtleOverlay(TurtleOverlays.TRANS_FLAG, x -> x
|
||||
.unlockedBy("has_dye", has(ingredients.dye()))
|
||||
.requires(ColourUtils.getDyeTag(DyeColor.LIGHT_BLUE))
|
||||
.requires(ColourUtils.getDyeTag(DyeColor.PINK))
|
||||
.requires(ColourUtils.getDyeTag(DyeColor.WHITE))
|
||||
.requires(Items.STICK)
|
||||
);
|
||||
|
||||
turtleOverlay(add, registries, TurtleOverlays.RAINBOW_FLAG, x -> x
|
||||
.unlockedBy("has_dye", inventoryChange(itemPredicate(ingredients.dye())))
|
||||
turtleOverlay(TurtleOverlays.RAINBOW_FLAG, x -> x
|
||||
.unlockedBy("has_dye", has(ingredients.dye()))
|
||||
.requires(ColourUtils.getDyeTag(DyeColor.RED))
|
||||
.requires(ColourUtils.getDyeTag(DyeColor.ORANGE))
|
||||
.requires(ColourUtils.getDyeTag(DyeColor.YELLOW))
|
||||
@@ -212,264 +178,234 @@ final class RecipeProvider extends net.minecraft.data.recipes.RecipeProvider {
|
||||
);
|
||||
}
|
||||
|
||||
private void turtleOverlay(RecipeOutput add, HolderLookup.Provider registries, ResourceKey<TurtleOverlay> overlay, Consumer<ShapelessSpecBuilder> build) {
|
||||
private void turtleOverlay(ResourceKey<TurtleOverlay> overlay, Consumer<ShapelessSpecBuilder> build) {
|
||||
var holder = registries.lookupOrThrow(overlay.registryKey()).getOrThrow(overlay);
|
||||
|
||||
for (var turtleItem : turtleItems()) {
|
||||
var name = RegistryHelper.getKeyOrThrow(BuiltInRegistries.ITEM, turtleItem);
|
||||
|
||||
var builder = ShapelessSpecBuilder
|
||||
.shapeless(RecipeCategory.REDSTONE, DataComponentUtil.createStack(turtleItem, ModRegistry.DataComponents.OVERLAY.get(), holder))
|
||||
var builder = customShapeless(RecipeCategory.REDSTONE, DataComponentUtil.createStack(turtleItem, ModRegistry.DataComponents.OVERLAY.get(), holder))
|
||||
.group(name.withSuffix("_overlay").toString())
|
||||
.unlockedBy("has_turtle", inventoryChange(turtleItem));
|
||||
.unlockedBy("has_turtle", has(turtleItem));
|
||||
build.accept(builder);
|
||||
builder
|
||||
.requires(turtleItem)
|
||||
.build(s -> new TransformShapelessRecipe(s, List.of(
|
||||
CopyComponents.builder(turtleItem).exclude(ModRegistry.DataComponents.OVERLAY.get()).build()
|
||||
)))
|
||||
.save(add, name.withSuffix("_overlays/" + overlay.location().getPath()));
|
||||
.save(output, name.withSuffix("_overlays/" + overlay.location().getPath()));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private void basicRecipes(RecipeOutput add) {
|
||||
ShapedRecipeBuilder
|
||||
.shaped(RecipeCategory.REDSTONE, ModRegistry.Items.CABLE.get(), 6)
|
||||
private void basicRecipes() {
|
||||
shaped(RecipeCategory.REDSTONE, ModRegistry.Items.CABLE.get(), 6)
|
||||
.pattern(" # ")
|
||||
.pattern("#R#")
|
||||
.pattern(" # ")
|
||||
.define('#', Items.STONE)
|
||||
.define('R', ingredients.redstone())
|
||||
.unlockedBy("has_computer", inventoryChange(COMPUTER))
|
||||
.unlockedBy("has_modem", inventoryChange(WIRED_MODEM))
|
||||
.save(add);
|
||||
.unlockedBy("has_computer", has(COMPUTER))
|
||||
.unlockedBy("has_modem", has(WIRED_MODEM))
|
||||
.save(output);
|
||||
|
||||
ShapedRecipeBuilder
|
||||
.shaped(RecipeCategory.REDSTONE, ModRegistry.Items.COMPUTER_NORMAL.get())
|
||||
shaped(RecipeCategory.REDSTONE, ModRegistry.Items.COMPUTER_NORMAL.get())
|
||||
.pattern("###")
|
||||
.pattern("#R#")
|
||||
.pattern("#G#")
|
||||
.define('#', Items.STONE)
|
||||
.define('R', ingredients.redstone())
|
||||
.define('G', ingredients.glassPane())
|
||||
.unlockedBy("has_redstone", inventoryChange(itemPredicate(ingredients.redstone())))
|
||||
.save(add);
|
||||
.unlockedBy("has_redstone", has(ingredients.redstone()))
|
||||
.save(output);
|
||||
|
||||
ShapedRecipeBuilder
|
||||
.shaped(RecipeCategory.REDSTONE, ModRegistry.Items.COMPUTER_ADVANCED.get())
|
||||
shaped(RecipeCategory.REDSTONE, ModRegistry.Items.COMPUTER_ADVANCED.get())
|
||||
.pattern("###")
|
||||
.pattern("#R#")
|
||||
.pattern("#G#")
|
||||
.define('#', ingredients.goldIngot())
|
||||
.define('R', ingredients.redstone())
|
||||
.define('G', ingredients.glassPane())
|
||||
.unlockedBy("has_components", inventoryChange(itemPredicate(ingredients.redstone()), itemPredicate(ingredients.goldIngot())))
|
||||
.save(add);
|
||||
.unlockedBy("has_components", inventoryTrigger(itemPredicate(ingredients.redstone()), itemPredicate(ingredients.goldIngot())))
|
||||
.save(output);
|
||||
|
||||
ShapedSpecBuilder
|
||||
.shaped(RecipeCategory.REDSTONE, ModRegistry.Items.COMPUTER_ADVANCED.get())
|
||||
customShaped(RecipeCategory.REDSTONE, ModRegistry.Items.COMPUTER_ADVANCED.get())
|
||||
.pattern("###")
|
||||
.pattern("#C#")
|
||||
.pattern("# #")
|
||||
.define('#', ingredients.goldIngot())
|
||||
.define('C', ModRegistry.Items.COMPUTER_NORMAL.get())
|
||||
.unlockedBy("has_components", inventoryChange(itemPredicate(ModRegistry.Items.COMPUTER_NORMAL.get()), itemPredicate(ingredients.goldIngot())))
|
||||
.unlockedBy("has_components", inventoryTrigger(itemPredicate(ModRegistry.Items.COMPUTER_NORMAL.get()), itemPredicate(ingredients.goldIngot())))
|
||||
.build(x -> new TransformShapedRecipe(x, List.of(new CopyComponents(ModRegistry.Items.COMPUTER_NORMAL.get()))))
|
||||
.save(add, ResourceLocation.fromNamespaceAndPath(ComputerCraftAPI.MOD_ID, "computer_advanced_upgrade"));
|
||||
.save(output, ResourceLocation.fromNamespaceAndPath(ComputerCraftAPI.MOD_ID, "computer_advanced_upgrade"));
|
||||
|
||||
ShapedRecipeBuilder
|
||||
.shaped(RecipeCategory.REDSTONE, ModRegistry.Items.COMPUTER_COMMAND.get())
|
||||
shaped(RecipeCategory.REDSTONE, ModRegistry.Items.COMPUTER_COMMAND.get())
|
||||
.pattern("###")
|
||||
.pattern("#R#")
|
||||
.pattern("#G#")
|
||||
.define('#', ingredients.goldIngot())
|
||||
.define('R', Items.COMMAND_BLOCK)
|
||||
.define('G', ingredients.glassPane())
|
||||
.unlockedBy("has_components", inventoryChange(Items.COMMAND_BLOCK))
|
||||
.save(add);
|
||||
.unlockedBy("has_components", has(Items.COMMAND_BLOCK))
|
||||
.save(output);
|
||||
|
||||
ShapedSpecBuilder
|
||||
.shaped(RecipeCategory.REDSTONE, ModRegistry.Items.TURTLE_NORMAL.get())
|
||||
customShaped(RecipeCategory.REDSTONE, ModRegistry.Items.TURTLE_NORMAL.get())
|
||||
.pattern("###")
|
||||
.pattern("#C#")
|
||||
.pattern("#I#")
|
||||
.define('#', ingredients.ironIngot())
|
||||
.define('C', ModRegistry.Items.COMPUTER_NORMAL.get())
|
||||
.define('I', ingredients.woodenChest())
|
||||
.unlockedBy("has_computer", inventoryChange(ModRegistry.Items.COMPUTER_NORMAL.get()))
|
||||
.unlockedBy("has_computer", has(ModRegistry.Items.COMPUTER_NORMAL.get()))
|
||||
.build(x -> new TransformShapedRecipe(x, List.of(new CopyComponents(ModRegistry.Items.COMPUTER_NORMAL.get()))))
|
||||
.save(add);
|
||||
.save(output);
|
||||
|
||||
ShapedSpecBuilder
|
||||
.shaped(RecipeCategory.REDSTONE, ModRegistry.Items.TURTLE_ADVANCED.get())
|
||||
customShaped(RecipeCategory.REDSTONE, ModRegistry.Items.TURTLE_ADVANCED.get())
|
||||
.pattern("###")
|
||||
.pattern("#C#")
|
||||
.pattern("#I#")
|
||||
.define('#', ingredients.goldIngot())
|
||||
.define('C', ModRegistry.Items.COMPUTER_ADVANCED.get())
|
||||
.define('I', ingredients.woodenChest())
|
||||
.unlockedBy("has_computer", inventoryChange(ModRegistry.Items.COMPUTER_NORMAL.get()))
|
||||
.unlockedBy("has_computer", has(ModRegistry.Items.COMPUTER_NORMAL.get()))
|
||||
.build(x -> new TransformShapedRecipe(x, List.of(new CopyComponents(ModRegistry.Items.COMPUTER_ADVANCED.get()))))
|
||||
.save(add);
|
||||
.save(output);
|
||||
|
||||
ShapedSpecBuilder
|
||||
.shaped(RecipeCategory.REDSTONE, ModRegistry.Items.TURTLE_ADVANCED.get())
|
||||
customShaped(RecipeCategory.REDSTONE, ModRegistry.Items.TURTLE_ADVANCED.get())
|
||||
.pattern("###")
|
||||
.pattern("#C#")
|
||||
.pattern(" B ")
|
||||
.define('#', ingredients.goldIngot())
|
||||
.define('C', ModRegistry.Items.TURTLE_NORMAL.get())
|
||||
.define('B', ingredients.goldBlock())
|
||||
.unlockedBy("has_components", inventoryChange(itemPredicate(ModRegistry.Items.TURTLE_NORMAL.get()), itemPredicate(ingredients.goldIngot())))
|
||||
.unlockedBy("has_components", inventoryTrigger(itemPredicate(ModRegistry.Items.TURTLE_NORMAL.get()), itemPredicate(ingredients.goldIngot())))
|
||||
.build(x -> new TransformShapedRecipe(x, List.of(new CopyComponents(ModRegistry.Items.TURTLE_NORMAL.get()))))
|
||||
.save(add, ResourceLocation.fromNamespaceAndPath(ComputerCraftAPI.MOD_ID, "turtle_advanced_upgrade"));
|
||||
.save(output, ResourceLocation.fromNamespaceAndPath(ComputerCraftAPI.MOD_ID, "turtle_advanced_upgrade"));
|
||||
|
||||
ShapedRecipeBuilder
|
||||
.shaped(RecipeCategory.REDSTONE, ModRegistry.Items.DISK_DRIVE.get())
|
||||
shaped(RecipeCategory.REDSTONE, ModRegistry.Items.DISK_DRIVE.get())
|
||||
.pattern("###")
|
||||
.pattern("#R#")
|
||||
.pattern("#R#")
|
||||
.define('#', Items.STONE)
|
||||
.define('R', ingredients.redstone())
|
||||
.unlockedBy("has_computer", inventoryChange(COMPUTER))
|
||||
.save(add);
|
||||
.unlockedBy("has_computer", has(COMPUTER))
|
||||
.save(output);
|
||||
|
||||
ShapedRecipeBuilder
|
||||
.shaped(RecipeCategory.REDSTONE, ModRegistry.Items.MONITOR_NORMAL.get())
|
||||
shaped(RecipeCategory.REDSTONE, ModRegistry.Items.MONITOR_NORMAL.get())
|
||||
.pattern("###")
|
||||
.pattern("#G#")
|
||||
.pattern("###")
|
||||
.define('#', Items.STONE)
|
||||
.define('G', ingredients.glassPane())
|
||||
.unlockedBy("has_computer", inventoryChange(COMPUTER))
|
||||
.save(add);
|
||||
.unlockedBy("has_computer", has(COMPUTER))
|
||||
.save(output);
|
||||
|
||||
ShapedRecipeBuilder
|
||||
.shaped(RecipeCategory.REDSTONE, ModRegistry.Items.MONITOR_ADVANCED.get(), 4)
|
||||
shaped(RecipeCategory.REDSTONE, ModRegistry.Items.MONITOR_ADVANCED.get(), 4)
|
||||
.pattern("###")
|
||||
.pattern("#G#")
|
||||
.pattern("###")
|
||||
.define('#', ingredients.goldIngot())
|
||||
.define('G', ingredients.glassPane())
|
||||
.unlockedBy("has_computer", inventoryChange(COMPUTER))
|
||||
.save(add);
|
||||
.unlockedBy("has_computer", has(COMPUTER))
|
||||
.save(output);
|
||||
|
||||
ShapedRecipeBuilder
|
||||
.shaped(RecipeCategory.REDSTONE, ModRegistry.Items.POCKET_COMPUTER_NORMAL.get())
|
||||
shaped(RecipeCategory.REDSTONE, ModRegistry.Items.POCKET_COMPUTER_NORMAL.get())
|
||||
.pattern("###")
|
||||
.pattern("#A#")
|
||||
.pattern("#G#")
|
||||
.define('#', Items.STONE)
|
||||
.define('A', Items.GOLDEN_APPLE)
|
||||
.define('G', ingredients.glassPane())
|
||||
.unlockedBy("has_computer", inventoryChange(COMPUTER))
|
||||
.unlockedBy("has_apple", inventoryChange(Items.GOLDEN_APPLE))
|
||||
.save(add);
|
||||
.unlockedBy("has_computer", has(COMPUTER))
|
||||
.unlockedBy("has_apple", has(Items.GOLDEN_APPLE))
|
||||
.save(output);
|
||||
|
||||
ShapedRecipeBuilder
|
||||
.shaped(RecipeCategory.REDSTONE, ModRegistry.Items.POCKET_COMPUTER_ADVANCED.get())
|
||||
shaped(RecipeCategory.REDSTONE, ModRegistry.Items.POCKET_COMPUTER_ADVANCED.get())
|
||||
.pattern("###")
|
||||
.pattern("#A#")
|
||||
.pattern("#G#")
|
||||
.define('#', ingredients.goldIngot())
|
||||
.define('A', Items.GOLDEN_APPLE)
|
||||
.define('G', ingredients.glassPane())
|
||||
.unlockedBy("has_computer", inventoryChange(COMPUTER))
|
||||
.unlockedBy("has_apple", inventoryChange(Items.GOLDEN_APPLE))
|
||||
.save(add);
|
||||
.unlockedBy("has_computer", has(COMPUTER))
|
||||
.unlockedBy("has_apple", has(Items.GOLDEN_APPLE))
|
||||
.save(output);
|
||||
|
||||
ShapedSpecBuilder
|
||||
.shaped(RecipeCategory.REDSTONE, ModRegistry.Items.POCKET_COMPUTER_ADVANCED.get())
|
||||
customShaped(RecipeCategory.REDSTONE, ModRegistry.Items.POCKET_COMPUTER_ADVANCED.get())
|
||||
.pattern("###")
|
||||
.pattern("#C#")
|
||||
.pattern("# #")
|
||||
.define('#', ingredients.goldIngot())
|
||||
.define('C', ModRegistry.Items.POCKET_COMPUTER_NORMAL.get())
|
||||
.unlockedBy("has_components", inventoryChange(itemPredicate(ModRegistry.Items.POCKET_COMPUTER_NORMAL.get()), itemPredicate(ingredients.goldIngot())))
|
||||
.unlockedBy("has_components", inventoryTrigger(itemPredicate(ModRegistry.Items.POCKET_COMPUTER_NORMAL.get()), itemPredicate(ingredients.goldIngot())))
|
||||
.build(x -> new TransformShapedRecipe(x, List.of(new CopyComponents(ModRegistry.Items.POCKET_COMPUTER_NORMAL.get()))))
|
||||
.save(add, ResourceLocation.fromNamespaceAndPath(ComputerCraftAPI.MOD_ID, "pocket_computer_advanced_upgrade"));
|
||||
.save(output, ResourceLocation.fromNamespaceAndPath(ComputerCraftAPI.MOD_ID, "pocket_computer_advanced_upgrade"));
|
||||
|
||||
ShapedRecipeBuilder
|
||||
.shaped(RecipeCategory.REDSTONE, ModRegistry.Items.PRINTER.get())
|
||||
shaped(RecipeCategory.REDSTONE, ModRegistry.Items.PRINTER.get())
|
||||
.pattern("###")
|
||||
.pattern("#R#")
|
||||
.pattern("#D#")
|
||||
.define('#', Items.STONE)
|
||||
.define('R', ingredients.redstone())
|
||||
.define('D', ingredients.dye())
|
||||
.unlockedBy("has_computer", inventoryChange(COMPUTER))
|
||||
.save(add);
|
||||
.unlockedBy("has_computer", has(COMPUTER))
|
||||
.save(output);
|
||||
|
||||
ShapedRecipeBuilder
|
||||
.shaped(RecipeCategory.REDSTONE, ModRegistry.Items.SPEAKER.get())
|
||||
shaped(RecipeCategory.REDSTONE, ModRegistry.Items.SPEAKER.get())
|
||||
.pattern("###")
|
||||
.pattern("#N#")
|
||||
.pattern("#R#")
|
||||
.define('#', Items.STONE)
|
||||
.define('N', Items.NOTE_BLOCK)
|
||||
.define('R', ingredients.redstone())
|
||||
.unlockedBy("has_computer", inventoryChange(COMPUTER))
|
||||
.save(add);
|
||||
.unlockedBy("has_computer", has(COMPUTER))
|
||||
.save(output);
|
||||
|
||||
ShapedRecipeBuilder
|
||||
.shaped(RecipeCategory.REDSTONE, ModRegistry.Items.WIRED_MODEM.get())
|
||||
shaped(RecipeCategory.REDSTONE, ModRegistry.Items.WIRED_MODEM.get())
|
||||
.pattern("###")
|
||||
.pattern("#R#")
|
||||
.pattern("###")
|
||||
.define('#', Items.STONE)
|
||||
.define('R', ingredients.redstone())
|
||||
.unlockedBy("has_computer", inventoryChange(COMPUTER))
|
||||
.unlockedBy("has_cable", inventoryChange(ModRegistry.Items.CABLE.get()))
|
||||
.save(add);
|
||||
.unlockedBy("has_computer", has(COMPUTER))
|
||||
.unlockedBy("has_cable", has(ModRegistry.Items.CABLE.get()))
|
||||
.save(output);
|
||||
|
||||
ShapelessRecipeBuilder
|
||||
.shapeless(RecipeCategory.REDSTONE, ModRegistry.Items.WIRED_MODEM_FULL.get())
|
||||
.requires(ModRegistry.Items.WIRED_MODEM.get())
|
||||
.unlockedBy("has_modem", inventoryChange(WIRED_MODEM))
|
||||
.save(add, ResourceLocation.fromNamespaceAndPath(ComputerCraftAPI.MOD_ID, "wired_modem_full_from"));
|
||||
ShapelessRecipeBuilder
|
||||
.shapeless(RecipeCategory.REDSTONE, ModRegistry.Items.WIRED_MODEM.get())
|
||||
.requires(ModRegistry.Items.WIRED_MODEM_FULL.get())
|
||||
.unlockedBy("has_modem", inventoryChange(WIRED_MODEM))
|
||||
.save(add, ResourceLocation.fromNamespaceAndPath(ComputerCraftAPI.MOD_ID, "wired_modem_full_to"));
|
||||
oneToOneConversionRecipe(ModRegistry.Items.WIRED_MODEM.get(), ModRegistry.Items.WIRED_MODEM_FULL.get(), null);
|
||||
oneToOneConversionRecipe(ModRegistry.Items.WIRED_MODEM_FULL.get(), ModRegistry.Items.WIRED_MODEM.get(), null);
|
||||
|
||||
ShapedRecipeBuilder
|
||||
.shaped(RecipeCategory.REDSTONE, ModRegistry.Items.WIRELESS_MODEM_NORMAL.get())
|
||||
shaped(RecipeCategory.REDSTONE, ModRegistry.Items.WIRELESS_MODEM_NORMAL.get())
|
||||
.pattern("###")
|
||||
.pattern("#E#")
|
||||
.pattern("###")
|
||||
.define('#', Items.STONE)
|
||||
.define('E', ingredients.enderPearl())
|
||||
.unlockedBy("has_computer", inventoryChange(COMPUTER))
|
||||
.save(add);
|
||||
.unlockedBy("has_computer", has(COMPUTER))
|
||||
.save(output);
|
||||
|
||||
ShapedRecipeBuilder
|
||||
.shaped(RecipeCategory.REDSTONE, ModRegistry.Items.WIRELESS_MODEM_ADVANCED.get())
|
||||
shaped(RecipeCategory.REDSTONE, ModRegistry.Items.WIRELESS_MODEM_ADVANCED.get())
|
||||
.pattern("###")
|
||||
.pattern("#E#")
|
||||
.pattern("###")
|
||||
.define('#', ingredients.goldIngot())
|
||||
.define('E', Items.ENDER_EYE)
|
||||
.unlockedBy("has_computer", inventoryChange(COMPUTER))
|
||||
.unlockedBy("has_wireless", inventoryChange(ModRegistry.Items.WIRELESS_MODEM_NORMAL.get()))
|
||||
.save(add);
|
||||
.unlockedBy("has_computer", has(COMPUTER))
|
||||
.unlockedBy("has_wireless", has(ModRegistry.Items.WIRELESS_MODEM_NORMAL.get()))
|
||||
.save(output);
|
||||
|
||||
ShapelessSpecBuilder
|
||||
.shapeless(RecipeCategory.DECORATIONS, playerHead("Cloudhunter", "6d074736-b1e9-4378-a99b-bd8777821c9c"))
|
||||
customShapeless(RecipeCategory.DECORATIONS, playerHead("Cloudhunter", "6d074736-b1e9-4378-a99b-bd8777821c9c"))
|
||||
.requires(ItemTags.SKULLS)
|
||||
.requires(ModRegistry.Items.MONITOR_NORMAL.get())
|
||||
.unlockedBy("has_monitor", inventoryChange(ModRegistry.Items.MONITOR_NORMAL.get()))
|
||||
.unlockedBy("has_monitor", has(ModRegistry.Items.MONITOR_NORMAL.get()))
|
||||
.build()
|
||||
.save(add, ResourceLocation.fromNamespaceAndPath(ComputerCraftAPI.MOD_ID, "skull_cloudy"));
|
||||
.save(output, ResourceLocation.fromNamespaceAndPath(ComputerCraftAPI.MOD_ID, "skull_cloudy"));
|
||||
|
||||
ShapelessSpecBuilder
|
||||
.shapeless(RecipeCategory.DECORATIONS, playerHead("dan200", "f3c8d69b-0776-4512-8434-d1b2165909eb"))
|
||||
customShapeless(RecipeCategory.DECORATIONS, playerHead("dan200", "f3c8d69b-0776-4512-8434-d1b2165909eb"))
|
||||
.requires(ItemTags.SKULLS)
|
||||
.requires(ModRegistry.Items.COMPUTER_ADVANCED.get())
|
||||
.unlockedBy("has_computer", inventoryChange(ModRegistry.Items.COMPUTER_ADVANCED.get()))
|
||||
.unlockedBy("has_computer", has(ModRegistry.Items.COMPUTER_ADVANCED.get()))
|
||||
.build()
|
||||
.save(add, ResourceLocation.fromNamespaceAndPath(ComputerCraftAPI.MOD_ID, "skull_dan200"));
|
||||
.save(output, ResourceLocation.fromNamespaceAndPath(ComputerCraftAPI.MOD_ID, "skull_dan200"));
|
||||
|
||||
var pages = Ingredient.of(
|
||||
ModRegistry.Items.PRINTED_PAGE.get(),
|
||||
@@ -477,76 +413,84 @@ final class RecipeProvider extends net.minecraft.data.recipes.RecipeProvider {
|
||||
Items.PAPER
|
||||
);
|
||||
|
||||
ShapelessSpecBuilder
|
||||
.shapeless(RecipeCategory.REDSTONE, ModRegistry.Items.PRINTED_PAGES.get())
|
||||
customShapeless(RecipeCategory.REDSTONE, ModRegistry.Items.PRINTED_PAGES.get())
|
||||
.requires(ingredients.string())
|
||||
.unlockedBy("has_printer", inventoryChange(ModRegistry.Items.PRINTER.get()))
|
||||
.unlockedBy("has_printer", has(ModRegistry.Items.PRINTER.get()))
|
||||
.build(x -> new PrintoutRecipe(x, pages, 2))
|
||||
.save(add);
|
||||
.save(output);
|
||||
|
||||
ShapelessSpecBuilder
|
||||
.shapeless(RecipeCategory.REDSTONE, ModRegistry.Items.PRINTED_BOOK.get())
|
||||
customShapeless(RecipeCategory.REDSTONE, ModRegistry.Items.PRINTED_BOOK.get())
|
||||
.requires(ingredients.leather())
|
||||
.requires(ingredients.string())
|
||||
.unlockedBy("has_printer", inventoryChange(ModRegistry.Items.PRINTER.get()))
|
||||
.unlockedBy("has_printer", has(ModRegistry.Items.PRINTER.get()))
|
||||
.build(x -> new PrintoutRecipe(x, pages, 1))
|
||||
.save(add);
|
||||
.save(output);
|
||||
|
||||
ShapedRecipeBuilder
|
||||
.shaped(RecipeCategory.REDSTONE, ModRegistry.Blocks.REDSTONE_RELAY.get())
|
||||
shaped(RecipeCategory.REDSTONE, ModRegistry.Blocks.REDSTONE_RELAY.get())
|
||||
.pattern("SRS")
|
||||
.pattern("RCR")
|
||||
.pattern("SRS")
|
||||
.define('S', Items.STONE)
|
||||
.define('R', ingredients.redstone())
|
||||
.define('C', ModRegistry.Blocks.CABLE.get())
|
||||
.unlockedBy("has_cable", inventoryChange(ModRegistry.Blocks.CABLE.get()))
|
||||
.save(add);
|
||||
.unlockedBy("has_cable", has(ModRegistry.Blocks.CABLE.get()))
|
||||
.save(output);
|
||||
}
|
||||
|
||||
private static DyeColor ofColour(Colour colour) {
|
||||
return DyeColor.byId(15 - colour.ordinal());
|
||||
}
|
||||
|
||||
private static Criterion<InventoryChangeTrigger.TriggerInstance> inventoryChange(TagKey<Item> stack) {
|
||||
return InventoryChangeTrigger.TriggerInstance.hasItems(itemPredicate(stack));
|
||||
}
|
||||
|
||||
private static Criterion<InventoryChangeTrigger.TriggerInstance> inventoryChange(ItemLike... stack) {
|
||||
return InventoryChangeTrigger.TriggerInstance.hasItems(stack);
|
||||
}
|
||||
|
||||
private static Criterion<InventoryChangeTrigger.TriggerInstance> inventoryChange(ItemPredicate... items) {
|
||||
private static Criterion<InventoryChangeTrigger.TriggerInstance> has(ItemLike... items) {
|
||||
return InventoryChangeTrigger.TriggerInstance.hasItems(items);
|
||||
}
|
||||
|
||||
private static ItemPredicate itemPredicate(ItemLike item) {
|
||||
return ItemPredicate.Builder.item().of(item).build();
|
||||
private ItemPredicate itemPredicate(ItemLike item) {
|
||||
return ItemPredicate.Builder.item().of(items, item).build();
|
||||
}
|
||||
|
||||
private static ItemPredicate itemPredicate(TagKey<Item> item) {
|
||||
return ItemPredicate.Builder.item().of(item).build();
|
||||
}
|
||||
|
||||
private static ItemPredicate itemPredicate(Ingredient ingredient) {
|
||||
var json = Ingredient.CODEC_NONEMPTY.encodeStart(JsonOps.INSTANCE, ingredient).getOrThrow();
|
||||
if (!(json instanceof JsonObject object)) throw new IllegalStateException("Unknown ingredient " + json);
|
||||
|
||||
if (object.has("item")) {
|
||||
var item = ItemStack.SIMPLE_ITEM_CODEC.parse(JsonOps.INSTANCE, object).getOrThrow();
|
||||
return itemPredicate(item.getItem());
|
||||
} else if (object.has("tag")) {
|
||||
return itemPredicate(TagKey.create(Registries.ITEM, ResourceLocation.parse(GsonHelper.getAsString(object, "tag"))));
|
||||
} else {
|
||||
throw new IllegalArgumentException("Unknown ingredient " + json);
|
||||
}
|
||||
private ItemPredicate itemPredicate(TagKey<Item> item) {
|
||||
return ItemPredicate.Builder.item().of(items, item).build();
|
||||
}
|
||||
|
||||
private static ItemStack playerHead(String name, String uuid) {
|
||||
return DataComponentUtil.createStack(Items.PLAYER_HEAD, DataComponents.PROFILE, new ResolvableProfile(new GameProfile(UUID.fromString(uuid), name)));
|
||||
}
|
||||
|
||||
private static void addSpecial(RecipeOutput add, Recipe<?> recipe) {
|
||||
add.accept(RegistryHelper.getKeyOrThrow(BuiltInRegistries.RECIPE_SERIALIZER, recipe.getSerializer()), recipe, null);
|
||||
private ShapedSpecBuilder customShaped(RecipeCategory category, ItemStack result) {
|
||||
return new ShapedSpecBuilder(items, category, result);
|
||||
}
|
||||
|
||||
private ShapedSpecBuilder customShaped(RecipeCategory category, ItemLike result) {
|
||||
return new ShapedSpecBuilder(items, category, new ItemStack(result));
|
||||
}
|
||||
|
||||
private ShapelessSpecBuilder customShapeless(RecipeCategory category, ItemStack result) {
|
||||
return new ShapelessSpecBuilder(items, category, result);
|
||||
}
|
||||
|
||||
private ShapelessSpecBuilder customShapeless(RecipeCategory category, ItemLike result) {
|
||||
return new ShapelessSpecBuilder(items, category, new ItemStack(result));
|
||||
}
|
||||
|
||||
private void special(Recipe<?> recipe) {
|
||||
var key = RegistryHelper.getKeyOrThrow(BuiltInRegistries.RECIPE_SERIALIZER, recipe.getSerializer());
|
||||
output.accept(recipeKey(key), recipe, null);
|
||||
}
|
||||
|
||||
public static ResourceKey<Recipe<?>> recipeKey(ResourceLocation key) {
|
||||
return ResourceKey.create(Registries.RECIPE, key);
|
||||
}
|
||||
|
||||
static class Runner extends net.minecraft.data.recipes.RecipeProvider.Runner {
|
||||
protected Runner(PackOutput output, CompletableFuture<HolderLookup.Provider> registries) {
|
||||
super(output, registries);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected RecipeProvider createRecipeProvider(HolderLookup.Provider registries, RecipeOutput output) {
|
||||
return new RecipeProvider(registries, output);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return "Recipes";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -2,10 +2,14 @@
|
||||
//
|
||||
// SPDX-License-Identifier: MPL-2.0
|
||||
|
||||
package dan200.computercraft.data;
|
||||
package dan200.computercraft.data.client;
|
||||
|
||||
import com.google.gson.JsonObject;
|
||||
import dan200.computercraft.api.ComputerCraftAPI;
|
||||
import dan200.computercraft.api.turtle.TurtleSide;
|
||||
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.shared.ModRegistry;
|
||||
import dan200.computercraft.shared.computer.blocks.ComputerBlock;
|
||||
import dan200.computercraft.shared.peripheral.diskdrive.DiskDriveBlock;
|
||||
@@ -16,13 +20,18 @@ 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;
|
||||
import net.minecraft.client.data.models.blockstates.*;
|
||||
import net.minecraft.client.data.models.model.*;
|
||||
import net.minecraft.client.renderer.item.EmptyModel;
|
||||
import net.minecraft.client.renderer.item.properties.conditional.HasComponent;
|
||||
import net.minecraft.core.Direction;
|
||||
import net.minecraft.data.models.BlockModelGenerators;
|
||||
import net.minecraft.data.models.blockstates.*;
|
||||
import net.minecraft.data.models.model.*;
|
||||
import net.minecraft.core.component.DataComponents;
|
||||
import net.minecraft.resources.ResourceLocation;
|
||||
import net.minecraft.world.level.block.Block;
|
||||
import net.minecraft.world.level.block.Blocks;
|
||||
import net.minecraft.world.level.block.state.properties.BlockStateProperties;
|
||||
import net.minecraft.world.level.block.state.properties.BooleanProperty;
|
||||
@@ -34,10 +43,10 @@ import java.util.Optional;
|
||||
import java.util.function.BiFunction;
|
||||
import java.util.function.Function;
|
||||
|
||||
import static net.minecraft.data.models.model.ModelLocationUtils.getModelLocation;
|
||||
import static net.minecraft.data.models.model.TextureMapping.getBlockTexture;
|
||||
import static net.minecraft.client.data.models.model.ModelLocationUtils.getModelLocation;
|
||||
import static net.minecraft.client.data.models.model.TextureMapping.getBlockTexture;
|
||||
|
||||
class BlockModelProvider {
|
||||
public class BlockModelProvider {
|
||||
private static final TextureSlot CURSOR = TextureSlot.create("cursor");
|
||||
private static final TextureSlot LEFT = TextureSlot.create("left");
|
||||
private static final TextureSlot RIGHT = TextureSlot.create("right");
|
||||
@@ -92,6 +101,8 @@ class BlockModelProvider {
|
||||
registerMonitor(generators, ModRegistry.Blocks.MONITOR_ADVANCED.get());
|
||||
|
||||
generators.createHorizontallyRotatedBlock(ModRegistry.Blocks.SPEAKER.get(), TexturedModel.ORIENTABLE_ONLY_TOP);
|
||||
registerSimpleItemModel(generators, ModRegistry.Blocks.SPEAKER.get());
|
||||
|
||||
registerDiskDrive(generators);
|
||||
registerPrinter(generators);
|
||||
|
||||
@@ -104,10 +115,10 @@ class BlockModelProvider {
|
||||
registerTurtleModem(generators, "block/turtle_modem_normal", "block/wireless_modem_normal_face");
|
||||
registerTurtleModem(generators, "block/turtle_modem_advanced", "block/wireless_modem_advanced_face");
|
||||
|
||||
generators.blockStateOutput.accept(MultiVariantGenerator.multiVariant(
|
||||
ModRegistry.Blocks.LECTERN.get(),
|
||||
Variant.variant().with(VariantProperties.MODEL, ModelLocationUtils.getModelLocation(Blocks.LECTERN))
|
||||
).with(createHorizontalFacingDispatch()));
|
||||
generators.blockStateOutput.accept(
|
||||
BlockModelGenerators.createSimpleBlock(ModRegistry.Blocks.LECTERN.get(), getModelLocation(Blocks.LECTERN))
|
||||
.with(createHorizontalFacingDispatch())
|
||||
);
|
||||
}
|
||||
|
||||
private static void registerDiskDrive(BlockModelGenerators generators) {
|
||||
@@ -127,7 +138,7 @@ class BlockModelProvider {
|
||||
);
|
||||
}))
|
||||
);
|
||||
generators.delegateItemModel(diskDrive, getModelLocation(diskDrive, "_empty"));
|
||||
generators.registerSimpleItemModel(diskDrive, getModelLocation(diskDrive, "_empty"));
|
||||
}
|
||||
|
||||
private static void registerPrinter(BlockModelGenerators generators) {
|
||||
@@ -155,7 +166,7 @@ class BlockModelProvider {
|
||||
);
|
||||
}))
|
||||
);
|
||||
generators.delegateItemModel(printer, getModelLocation(printer, "_empty"));
|
||||
generators.registerSimpleItemModel(printer, getModelLocation(printer, "_empty"));
|
||||
}
|
||||
|
||||
private static void registerComputer(BlockModelGenerators generators, ComputerBlock<?> block) {
|
||||
@@ -174,10 +185,17 @@ class BlockModelProvider {
|
||||
);
|
||||
}))
|
||||
);
|
||||
generators.delegateItemModel(block, getModelLocation(block, "_blinking"));
|
||||
generators.registerSimpleItemModel(block, getModelLocation(block, "_blinking"));
|
||||
}
|
||||
|
||||
private static void registerTurtle(BlockModelGenerators generators, TurtleBlock block) {
|
||||
// The actual turtle blockstate is an empty model with just the partcles.
|
||||
var particleModel = ModelTemplates.PARTICLE_ONLY.createWithSuffix(
|
||||
block, "_particle", TextureMapping.particle(getBlockTexture(block, "_front")), generators.modelOutput
|
||||
);
|
||||
generators.blockStateOutput.accept(BlockModelGenerators.createSimpleBlock(block, particleModel));
|
||||
|
||||
// We then register the full model for use in items and the BE renderer.
|
||||
var model = TURTLE.create(block, new TextureMapping()
|
||||
.put(TextureSlot.FRONT, getBlockTexture(block, "_front"))
|
||||
.put(TextureSlot.BACK, getBlockTexture(block, "_back"))
|
||||
@@ -188,17 +206,21 @@ class BlockModelProvider {
|
||||
.put(BACKPACK, getBlockTexture(block, "_backpack")),
|
||||
generators.modelOutput
|
||||
);
|
||||
generators.blockStateOutput.accept(
|
||||
MultiVariantGenerator.multiVariant(block, Variant.variant().with(VariantProperties.MODEL, model))
|
||||
.with(createHorizontalFacingDispatch())
|
||||
);
|
||||
|
||||
generators.modelOutput.accept(getModelLocation(block.asItem()), () -> {
|
||||
var out = new JsonObject();
|
||||
out.addProperty("loader", "computercraft:turtle");
|
||||
out.addProperty("model", model.toString());
|
||||
return out;
|
||||
});
|
||||
generators.itemModelOutput.accept(block.asItem(), ItemModelUtils.composite(
|
||||
ItemModelUtils.conditional(
|
||||
new HasComponent(DataComponents.DYED_COLOR, false),
|
||||
ItemModelUtils.plainModel(TurtleBlockEntityRenderer.COLOUR_TURTLE_MODEL),
|
||||
ItemModelUtils.plainModel(model)
|
||||
),
|
||||
new TurtleUpgradeModel.Unbaked(TurtleSide.LEFT, model),
|
||||
new TurtleUpgradeModel.Unbaked(TurtleSide.RIGHT, model),
|
||||
new TurtleOverlayModel.Unbaked(model),
|
||||
ItemModelUtils.isXmas(
|
||||
ItemModelUtils.conditional(TurtleShowElfOverlay.create(), ItemModelUtils.plainModel(TurtleOverlay.ELF_MODEL), new EmptyModel.Unbaked()),
|
||||
new EmptyModel.Unbaked()
|
||||
)
|
||||
));
|
||||
}
|
||||
|
||||
private static void registerWirelessModem(BlockModelGenerators generators, WirelessModemBlock block) {
|
||||
@@ -207,7 +229,7 @@ class BlockModelProvider {
|
||||
.with(createModelDispatch(WirelessModemBlock.ON,
|
||||
on -> modemModel(generators, getModelLocation(block, on ? "_on" : "_off"), getBlockTexture(block, "_face" + (on ? "_on" : "")))
|
||||
)));
|
||||
generators.delegateItemModel(block, getModelLocation(block, "_off"));
|
||||
generators.registerSimpleItemModel(block, getModelLocation(block, "_off"));
|
||||
}
|
||||
|
||||
private static void registerWiredModems(BlockModelGenerators generators) {
|
||||
@@ -227,8 +249,8 @@ class BlockModelProvider {
|
||||
);
|
||||
})));
|
||||
|
||||
generators.delegateItemModel(fullBlock, getModelLocation(fullBlock, "_off"));
|
||||
generators.delegateItemModel(ModRegistry.Items.WIRED_MODEM.get(), ResourceLocation.fromNamespaceAndPath(ComputerCraftAPI.MOD_ID, "block/wired_modem_off"));
|
||||
generators.registerSimpleItemModel(fullBlock, getModelLocation(fullBlock, "_off"));
|
||||
generators.registerSimpleItemModel(ModRegistry.Items.WIRED_MODEM.get(), ResourceLocation.fromNamespaceAndPath(ComputerCraftAPI.MOD_ID, "block/wired_modem_off"));
|
||||
}
|
||||
|
||||
private static ResourceLocation modemModel(BlockModelGenerators generators, ResourceLocation name, ResourceLocation texture) {
|
||||
@@ -264,7 +286,7 @@ class BlockModelProvider {
|
||||
.with(createVerticalFacingDispatch(MonitorBlock.ORIENTATION))
|
||||
.with(createModelDispatch(MonitorBlock.STATE, edge -> getModelLocation(block, edge == MonitorEdgeState.NONE ? "" : "_" + edge.getSerializedName())))
|
||||
);
|
||||
generators.delegateItemModel(block, monitorModel(generators, block, "_item", 15, 4, 0, 32));
|
||||
generators.registerSimpleItemModel(block, monitorModel(generators, block, "_item", 15, 4, 0, 32));
|
||||
}
|
||||
|
||||
private static ResourceLocation monitorModel(BlockModelGenerators generators, MonitorBlock block, String corners, int front, int side, int top, int back) {
|
||||
@@ -355,17 +377,13 @@ class BlockModelProvider {
|
||||
}
|
||||
|
||||
generators.blockStateOutput.accept(generator);
|
||||
generators.registerSimpleItemModel(ModRegistry.Items.CABLE.get(), getModelLocation(ModRegistry.Items.CABLE.get()));
|
||||
}
|
||||
|
||||
private static void registerRedstoneControl(BlockModelGenerators generators) {
|
||||
var redstoneControl = ModRegistry.Blocks.REDSTONE_RELAY.get();
|
||||
var model = ModelTemplates.CUBE_ORIENTABLE_TOP_BOTTOM.create(
|
||||
redstoneControl, TextureMapping.orientableCube(redstoneControl), generators.modelOutput
|
||||
);
|
||||
generators.blockStateOutput.accept(
|
||||
MultiVariantGenerator.multiVariant(redstoneControl, Variant.variant().with(VariantProperties.MODEL, model))
|
||||
.with(createHorizontalFacingDispatch())
|
||||
);
|
||||
generators.createHorizontallyRotatedBlock(redstoneControl, TexturedModel.ORIENTABLE);
|
||||
registerSimpleItemModel(generators, redstoneControl);
|
||||
}
|
||||
|
||||
|
||||
@@ -396,6 +414,10 @@ class BlockModelProvider {
|
||||
registerTurtleUpgrade(generators, name + "_on", texture + "_on");
|
||||
}
|
||||
|
||||
private static void registerSimpleItemModel(BlockModelGenerators generators, Block block) {
|
||||
generators.registerSimpleItemModel(block, ModelLocationUtils.getModelLocation(block));
|
||||
}
|
||||
|
||||
private static VariantProperties.Rotation toXAngle(Direction direction) {
|
||||
return switch (direction) {
|
||||
default -> VariantProperties.Rotation.R0;
|
@@ -0,0 +1,129 @@
|
||||
// SPDX-FileCopyrightText: 2022 The CC: Tweaked Developers
|
||||
//
|
||||
// SPDX-License-Identifier: MPL-2.0
|
||||
|
||||
package dan200.computercraft.data.client;
|
||||
|
||||
import dan200.computercraft.api.ComputerCraftAPI;
|
||||
import dan200.computercraft.client.item.colour.PocketComputerLight;
|
||||
import dan200.computercraft.client.item.properties.PocketComputerStateProperty;
|
||||
import dan200.computercraft.core.util.Colour;
|
||||
import dan200.computercraft.shared.ModRegistry;
|
||||
import dan200.computercraft.shared.computer.core.ComputerState;
|
||||
import net.minecraft.client.color.item.Dye;
|
||||
import net.minecraft.client.data.models.ItemModelGenerators;
|
||||
import net.minecraft.client.data.models.model.*;
|
||||
import net.minecraft.client.renderer.item.BlockModelWrapper;
|
||||
import net.minecraft.client.renderer.item.ItemModel;
|
||||
import net.minecraft.client.renderer.item.properties.conditional.HasComponent;
|
||||
import net.minecraft.core.component.DataComponents;
|
||||
import net.minecraft.resources.ResourceLocation;
|
||||
import net.minecraft.world.item.Item;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
|
||||
import static net.minecraft.client.data.models.model.ModelLocationUtils.getModelLocation;
|
||||
|
||||
public final class ItemModelProvider {
|
||||
private static final ResourceLocation POCKET_COMPUTER_COLOUR = ResourceLocation.fromNamespaceAndPath(ComputerCraftAPI.MOD_ID, "item/pocket_computer_colour");
|
||||
|
||||
private ItemModelProvider() {
|
||||
}
|
||||
|
||||
public static void addItemModels(ItemModelGenerators generators) {
|
||||
registerDisk(generators, ModRegistry.Items.DISK.get(), Colour.WHITE.getARGB());
|
||||
registerDisk(generators, ModRegistry.Items.TREASURE_DISK.get(), Colour.BLACK.getARGB());
|
||||
|
||||
registerPocketComputer(generators, ModRegistry.Items.POCKET_COMPUTER_NORMAL.get());
|
||||
registerPocketComputer(generators, ModRegistry.Items.POCKET_COMPUTER_ADVANCED.get());
|
||||
registerPocketComputerModels(generators, POCKET_COMPUTER_COLOUR);
|
||||
|
||||
generators.generateFlatItem(ModRegistry.Items.PRINTED_BOOK.get(), ModelTemplates.FLAT_ITEM);
|
||||
generators.generateFlatItem(ModRegistry.Items.PRINTED_PAGE.get(), ModelTemplates.FLAT_ITEM);
|
||||
generators.generateFlatItem(ModRegistry.Items.PRINTED_PAGES.get(), ModelTemplates.FLAT_ITEM);
|
||||
}
|
||||
|
||||
private static void registerPocketComputerModels(ItemModelGenerators generators, ResourceLocation id) {
|
||||
createFlatItem(generators, id.withSuffix("_blinking"),
|
||||
ResourceLocation.fromNamespaceAndPath(ComputerCraftAPI.MOD_ID, "item/pocket_computer_blink"),
|
||||
id,
|
||||
ResourceLocation.fromNamespaceAndPath(ComputerCraftAPI.MOD_ID, "item/pocket_computer_light")
|
||||
);
|
||||
|
||||
createFlatItem(generators, id.withSuffix("_on"),
|
||||
ResourceLocation.fromNamespaceAndPath(ComputerCraftAPI.MOD_ID, "item/pocket_computer_on"),
|
||||
id,
|
||||
ResourceLocation.fromNamespaceAndPath(ComputerCraftAPI.MOD_ID, "item/pocket_computer_light")
|
||||
);
|
||||
|
||||
createFlatItem(generators, id,
|
||||
ResourceLocation.fromNamespaceAndPath(ComputerCraftAPI.MOD_ID, "item/pocket_computer_frame"),
|
||||
id
|
||||
);
|
||||
}
|
||||
|
||||
private static void registerPocketComputer(ItemModelGenerators generators, Item item) {
|
||||
registerPocketComputerModels(generators, getModelLocation(item));
|
||||
|
||||
generators.itemModelOutput.accept(item, ItemModelUtils.conditional(
|
||||
new HasComponent(DataComponents.DYED_COLOR, false),
|
||||
createPocketModel(POCKET_COMPUTER_COLOUR),
|
||||
createPocketModel(getModelLocation(item))
|
||||
));
|
||||
}
|
||||
|
||||
private static ItemModel.Unbaked createPocketModel(ResourceLocation id) {
|
||||
var tints = List.of(
|
||||
ItemModelUtils.constantTint(-1),
|
||||
new Dye(-1),
|
||||
new PocketComputerLight(Colour.BLACK.getARGB())
|
||||
);
|
||||
|
||||
return ItemModelUtils.select(PocketComputerStateProperty.create(),
|
||||
ItemModelUtils.when(ComputerState.OFF, new BlockModelWrapper.Unbaked(id, tints)),
|
||||
ItemModelUtils.when(ComputerState.ON, new BlockModelWrapper.Unbaked(id.withSuffix("_on"), tints)),
|
||||
ItemModelUtils.when(ComputerState.BLINKING, new BlockModelWrapper.Unbaked(id.withSuffix("_blinking"), tints))
|
||||
);
|
||||
}
|
||||
|
||||
private static void registerDisk(ItemModelGenerators generators, Item item, int colour) {
|
||||
var model = getModelLocation(item);
|
||||
createFlatItem(generators, model,
|
||||
ResourceLocation.fromNamespaceAndPath(ComputerCraftAPI.MOD_ID, "item/disk_frame"),
|
||||
ResourceLocation.fromNamespaceAndPath(ComputerCraftAPI.MOD_ID, "item/disk_colour")
|
||||
);
|
||||
|
||||
generators.itemModelOutput.accept(item, new BlockModelWrapper.Unbaked(model, List.of(
|
||||
ItemModelUtils.constantTint(-1),
|
||||
new Dye(colour)
|
||||
)));
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate a flat item from an arbitrary number of layers.
|
||||
*
|
||||
* @param generators The current item generator helper.
|
||||
* @param model The model we're writing to.
|
||||
* @param textures The textures which make up this model.
|
||||
* @see net.minecraft.client.renderer.block.model.ItemModelGenerator The parser for this file format.
|
||||
*/
|
||||
private static void createFlatItem(ItemModelGenerators generators, ResourceLocation model, ResourceLocation... textures) {
|
||||
if (textures.length > 5) throw new IndexOutOfBoundsException("Too many layers");
|
||||
if (textures.length == 0) throw new IndexOutOfBoundsException("Must have at least one texture");
|
||||
if (textures.length == 1) {
|
||||
ModelTemplates.FLAT_ITEM.create(model, TextureMapping.layer0(textures[0]), generators.modelOutput);
|
||||
return;
|
||||
}
|
||||
|
||||
var slots = new TextureSlot[textures.length];
|
||||
var mapping = new TextureMapping();
|
||||
for (var i = 0; i < textures.length; i++) {
|
||||
var slot = slots[i] = TextureSlot.create("layer" + i);
|
||||
mapping.put(slot, textures[i]);
|
||||
}
|
||||
|
||||
new ModelTemplate(Optional.of(ResourceLocation.withDefaultNamespace("item/generated")), Optional.empty(), slots)
|
||||
.create(model, mapping, generators.modelOutput);
|
||||
}
|
||||
}
|
@@ -10,9 +10,12 @@ import net.minecraft.advancements.AdvancementRequirements;
|
||||
import net.minecraft.advancements.AdvancementRewards;
|
||||
import net.minecraft.advancements.Criterion;
|
||||
import net.minecraft.advancements.critereon.RecipeUnlockedTrigger;
|
||||
import net.minecraft.core.HolderGetter;
|
||||
import net.minecraft.core.registries.Registries;
|
||||
import net.minecraft.data.recipes.RecipeBuilder;
|
||||
import net.minecraft.data.recipes.RecipeCategory;
|
||||
import net.minecraft.data.recipes.RecipeOutput;
|
||||
import net.minecraft.resources.ResourceKey;
|
||||
import net.minecraft.resources.ResourceLocation;
|
||||
import net.minecraft.world.item.Item;
|
||||
import net.minecraft.world.item.ItemStack;
|
||||
@@ -30,12 +33,14 @@ import java.util.function.Function;
|
||||
* @see ShapelessSpecBuilder
|
||||
*/
|
||||
public abstract class AbstractRecipeBuilder<S extends AbstractRecipeBuilder<S, O>, O> {
|
||||
protected final HolderGetter<Item> items;
|
||||
private final RecipeCategory category;
|
||||
protected final ItemStack result;
|
||||
private String group = "";
|
||||
private final Map<String, Criterion<?>> criteria = new LinkedHashMap<>();
|
||||
|
||||
protected AbstractRecipeBuilder(RecipeCategory category, ItemStack result) {
|
||||
protected AbstractRecipeBuilder(HolderGetter<Item> items, RecipeCategory category, ItemStack result) {
|
||||
this.items = items;
|
||||
this.category = category;
|
||||
this.result = result;
|
||||
}
|
||||
@@ -112,17 +117,23 @@ public abstract class AbstractRecipeBuilder<S extends AbstractRecipeBuilder<S, O
|
||||
|
||||
public void save(RecipeOutput output, ResourceLocation id) {
|
||||
if (criteria.isEmpty()) throw new IllegalStateException("No way of obtaining recipe " + id);
|
||||
|
||||
var key = recipeKey(id);
|
||||
var advancement = output.advancement()
|
||||
.addCriterion("has_the_recipe", RecipeUnlockedTrigger.unlocked(id))
|
||||
.rewards(AdvancementRewards.Builder.recipe(id))
|
||||
.addCriterion("has_the_recipe", RecipeUnlockedTrigger.unlocked(key))
|
||||
.rewards(AdvancementRewards.Builder.recipe(key))
|
||||
.requirements(AdvancementRequirements.Strategy.OR);
|
||||
for (var entry : criteria.entrySet()) advancement.addCriterion(entry.getKey(), entry.getValue());
|
||||
|
||||
output.accept(id, recipe, advancement.build(id.withPrefix("recipes/" + category.getFolderName() + "/")));
|
||||
output.accept(key, recipe, advancement.build(id.withPrefix("recipes/" + category.getFolderName() + "/")));
|
||||
}
|
||||
|
||||
public void save(RecipeOutput output) {
|
||||
save(output, RecipeBuilder.getDefaultRecipeId(result));
|
||||
}
|
||||
}
|
||||
|
||||
protected static ResourceKey<Recipe<?>> recipeKey(ResourceLocation key) {
|
||||
return ResourceKey.create(Registries.RECIPE, key);
|
||||
}
|
||||
}
|
||||
|
@@ -6,6 +6,7 @@ package dan200.computercraft.data.recipe;
|
||||
|
||||
import dan200.computercraft.shared.recipe.RecipeProperties;
|
||||
import dan200.computercraft.shared.recipe.ShapedRecipeSpec;
|
||||
import net.minecraft.core.HolderGetter;
|
||||
import net.minecraft.data.recipes.RecipeCategory;
|
||||
import net.minecraft.data.recipes.ShapedRecipeBuilder;
|
||||
import net.minecraft.tags.TagKey;
|
||||
@@ -27,16 +28,8 @@ public final class ShapedSpecBuilder extends AbstractRecipeBuilder<ShapedSpecBui
|
||||
private final List<String> rows = new ArrayList<>();
|
||||
private final Map<Character, Ingredient> key = new LinkedHashMap<>();
|
||||
|
||||
private ShapedSpecBuilder(RecipeCategory category, ItemStack result) {
|
||||
super(category, result);
|
||||
}
|
||||
|
||||
public static ShapedSpecBuilder shaped(RecipeCategory category, ItemStack result) {
|
||||
return new ShapedSpecBuilder(category, result);
|
||||
}
|
||||
|
||||
public static ShapedSpecBuilder shaped(RecipeCategory category, ItemLike result) {
|
||||
return new ShapedSpecBuilder(category, new ItemStack(result));
|
||||
public ShapedSpecBuilder(HolderGetter<Item> items, RecipeCategory category, ItemStack result) {
|
||||
super(items, category, result);
|
||||
}
|
||||
|
||||
public ShapedSpecBuilder define(char key, Ingredient ingredient) {
|
||||
@@ -48,7 +41,7 @@ public final class ShapedSpecBuilder extends AbstractRecipeBuilder<ShapedSpecBui
|
||||
}
|
||||
|
||||
public ShapedSpecBuilder define(char key, TagKey<Item> tag) {
|
||||
return this.define(key, Ingredient.of(tag));
|
||||
return this.define(key, Ingredient.of(items.getOrThrow(tag)));
|
||||
}
|
||||
|
||||
public ShapedSpecBuilder define(char key, ItemLike item) {
|
||||
|
@@ -6,6 +6,7 @@ package dan200.computercraft.data.recipe;
|
||||
|
||||
import dan200.computercraft.shared.recipe.RecipeProperties;
|
||||
import dan200.computercraft.shared.recipe.ShapelessRecipeSpec;
|
||||
import net.minecraft.core.HolderGetter;
|
||||
import net.minecraft.core.NonNullList;
|
||||
import net.minecraft.data.recipes.RecipeCategory;
|
||||
import net.minecraft.data.recipes.ShapelessRecipeBuilder;
|
||||
@@ -21,20 +22,12 @@ import net.minecraft.world.level.ItemLike;
|
||||
public final class ShapelessSpecBuilder extends AbstractRecipeBuilder<ShapelessSpecBuilder, ShapelessRecipeSpec> {
|
||||
private final NonNullList<Ingredient> ingredients = NonNullList.create();
|
||||
|
||||
private ShapelessSpecBuilder(RecipeCategory category, ItemStack result) {
|
||||
super(category, result);
|
||||
}
|
||||
|
||||
public static ShapelessSpecBuilder shapeless(RecipeCategory category, ItemStack result) {
|
||||
return new ShapelessSpecBuilder(category, result);
|
||||
}
|
||||
|
||||
public static ShapelessSpecBuilder shapeless(RecipeCategory category, ItemLike result) {
|
||||
return new ShapelessSpecBuilder(category, new ItemStack(result));
|
||||
public ShapelessSpecBuilder(HolderGetter<Item> items, RecipeCategory category, ItemStack result) {
|
||||
super(items, category, result);
|
||||
}
|
||||
|
||||
public ShapelessSpecBuilder requires(Ingredient ingredient, int count) {
|
||||
for (int i = 0; i < count; i++) ingredients.add(ingredient);
|
||||
for (var i = 0; i < count; i++) ingredients.add(ingredient);
|
||||
return this;
|
||||
}
|
||||
|
||||
@@ -43,15 +36,15 @@ public final class ShapelessSpecBuilder extends AbstractRecipeBuilder<ShapelessS
|
||||
}
|
||||
|
||||
public ShapelessSpecBuilder requires(ItemLike item) {
|
||||
return requires(Ingredient.of(new ItemStack(item)));
|
||||
return requires(Ingredient.of(item));
|
||||
}
|
||||
|
||||
public ShapelessSpecBuilder requires(ItemLike item, int count) {
|
||||
return requires(Ingredient.of(new ItemStack(item)), count);
|
||||
return requires(Ingredient.of(item), count);
|
||||
}
|
||||
|
||||
public ShapelessSpecBuilder requires(TagKey<Item> item) {
|
||||
return requires(Ingredient.of(item));
|
||||
return requires(Ingredient.of(items.getOrThrow(item)));
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"variants": {
|
||||
"facing=east": {"model": "computercraft:block/redstone_relay", "y": 90},
|
||||
"facing=north": {"model": "computercraft:block/redstone_relay", "y": 0},
|
||||
"facing=north": {"model": "computercraft:block/redstone_relay"},
|
||||
"facing=south": {"model": "computercraft:block/redstone_relay", "y": 180},
|
||||
"facing=west": {"model": "computercraft:block/redstone_relay", "y": 270}
|
||||
}
|
||||
|
@@ -1,8 +1 @@
|
||||
{
|
||||
"variants": {
|
||||
"facing=east": {"model": "computercraft:block/turtle_advanced", "y": 90},
|
||||
"facing=north": {"model": "computercraft:block/turtle_advanced", "y": 0},
|
||||
"facing=south": {"model": "computercraft:block/turtle_advanced", "y": 180},
|
||||
"facing=west": {"model": "computercraft:block/turtle_advanced", "y": 270}
|
||||
}
|
||||
}
|
||||
{"variants": {"": {"model": "computercraft:block/turtle_advanced_particle"}}}
|
||||
|
@@ -1,8 +1 @@
|
||||
{
|
||||
"variants": {
|
||||
"facing=east": {"model": "computercraft:block/turtle_normal", "y": 90},
|
||||
"facing=north": {"model": "computercraft:block/turtle_normal", "y": 0},
|
||||
"facing=south": {"model": "computercraft:block/turtle_normal", "y": 180},
|
||||
"facing=west": {"model": "computercraft:block/turtle_normal", "y": 270}
|
||||
}
|
||||
}
|
||||
{"variants": {"": {"model": "computercraft:block/turtle_normal_particle"}}}
|
||||
|
1
projects/common/src/generated/resources/assets/computercraft/items/cable.json
generated
Normal file
1
projects/common/src/generated/resources/assets/computercraft/items/cable.json
generated
Normal file
@@ -0,0 +1 @@
|
||||
{"model": {"type": "minecraft:model", "model": "computercraft:item/cable"}}
|
1
projects/common/src/generated/resources/assets/computercraft/items/computer_advanced.json
generated
Normal file
1
projects/common/src/generated/resources/assets/computercraft/items/computer_advanced.json
generated
Normal file
@@ -0,0 +1 @@
|
||||
{"model": {"type": "minecraft:model", "model": "computercraft:block/computer_advanced_blinking"}}
|
1
projects/common/src/generated/resources/assets/computercraft/items/computer_command.json
generated
Normal file
1
projects/common/src/generated/resources/assets/computercraft/items/computer_command.json
generated
Normal file
@@ -0,0 +1 @@
|
||||
{"model": {"type": "minecraft:model", "model": "computercraft:block/computer_command_blinking"}}
|
1
projects/common/src/generated/resources/assets/computercraft/items/computer_normal.json
generated
Normal file
1
projects/common/src/generated/resources/assets/computercraft/items/computer_normal.json
generated
Normal file
@@ -0,0 +1 @@
|
||||
{"model": {"type": "minecraft:model", "model": "computercraft:block/computer_normal_blinking"}}
|
7
projects/common/src/generated/resources/assets/computercraft/items/disk.json
generated
Normal file
7
projects/common/src/generated/resources/assets/computercraft/items/disk.json
generated
Normal file
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"model": {
|
||||
"type": "minecraft:model",
|
||||
"model": "computercraft:item/disk",
|
||||
"tints": [{"type": "minecraft:constant", "value": -1}, {"type": "minecraft:dye", "default": -986896}]
|
||||
}
|
||||
}
|
1
projects/common/src/generated/resources/assets/computercraft/items/disk_drive.json
generated
Normal file
1
projects/common/src/generated/resources/assets/computercraft/items/disk_drive.json
generated
Normal file
@@ -0,0 +1 @@
|
||||
{"model": {"type": "minecraft:model", "model": "computercraft:block/disk_drive_empty"}}
|
1
projects/common/src/generated/resources/assets/computercraft/items/monitor_advanced.json
generated
Normal file
1
projects/common/src/generated/resources/assets/computercraft/items/monitor_advanced.json
generated
Normal file
@@ -0,0 +1 @@
|
||||
{"model": {"type": "minecraft:model", "model": "computercraft:block/monitor_advanced_item"}}
|
1
projects/common/src/generated/resources/assets/computercraft/items/monitor_normal.json
generated
Normal file
1
projects/common/src/generated/resources/assets/computercraft/items/monitor_normal.json
generated
Normal file
@@ -0,0 +1 @@
|
||||
{"model": {"type": "minecraft:model", "model": "computercraft:block/monitor_normal_item"}}
|
91
projects/common/src/generated/resources/assets/computercraft/items/pocket_computer_advanced.json
generated
Normal file
91
projects/common/src/generated/resources/assets/computercraft/items/pocket_computer_advanced.json
generated
Normal file
@@ -0,0 +1,91 @@
|
||||
{
|
||||
"model": {
|
||||
"type": "minecraft:condition",
|
||||
"component": "minecraft:dyed_color",
|
||||
"on_false": {
|
||||
"type": "minecraft:select",
|
||||
"cases": [
|
||||
{
|
||||
"model": {
|
||||
"type": "minecraft:model",
|
||||
"model": "computercraft:item/pocket_computer_advanced",
|
||||
"tints": [
|
||||
{"type": "minecraft:constant", "value": -1},
|
||||
{"type": "minecraft:dye", "default": -1},
|
||||
{"type": "computercraft:pocket_computer_light", "default": -15658735}
|
||||
]
|
||||
},
|
||||
"when": "off"
|
||||
},
|
||||
{
|
||||
"model": {
|
||||
"type": "minecraft:model",
|
||||
"model": "computercraft:item/pocket_computer_advanced_on",
|
||||
"tints": [
|
||||
{"type": "minecraft:constant", "value": -1},
|
||||
{"type": "minecraft:dye", "default": -1},
|
||||
{"type": "computercraft:pocket_computer_light", "default": -15658735}
|
||||
]
|
||||
},
|
||||
"when": "on"
|
||||
},
|
||||
{
|
||||
"model": {
|
||||
"type": "minecraft:model",
|
||||
"model": "computercraft:item/pocket_computer_advanced_blinking",
|
||||
"tints": [
|
||||
{"type": "minecraft:constant", "value": -1},
|
||||
{"type": "minecraft:dye", "default": -1},
|
||||
{"type": "computercraft:pocket_computer_light", "default": -15658735}
|
||||
]
|
||||
},
|
||||
"when": "blinking"
|
||||
}
|
||||
],
|
||||
"property": "computercraft:pocket_computer_state"
|
||||
},
|
||||
"on_true": {
|
||||
"type": "minecraft:select",
|
||||
"cases": [
|
||||
{
|
||||
"model": {
|
||||
"type": "minecraft:model",
|
||||
"model": "computercraft:item/pocket_computer_colour",
|
||||
"tints": [
|
||||
{"type": "minecraft:constant", "value": -1},
|
||||
{"type": "minecraft:dye", "default": -1},
|
||||
{"type": "computercraft:pocket_computer_light", "default": -15658735}
|
||||
]
|
||||
},
|
||||
"when": "off"
|
||||
},
|
||||
{
|
||||
"model": {
|
||||
"type": "minecraft:model",
|
||||
"model": "computercraft:item/pocket_computer_colour_on",
|
||||
"tints": [
|
||||
{"type": "minecraft:constant", "value": -1},
|
||||
{"type": "minecraft:dye", "default": -1},
|
||||
{"type": "computercraft:pocket_computer_light", "default": -15658735}
|
||||
]
|
||||
},
|
||||
"when": "on"
|
||||
},
|
||||
{
|
||||
"model": {
|
||||
"type": "minecraft:model",
|
||||
"model": "computercraft:item/pocket_computer_colour_blinking",
|
||||
"tints": [
|
||||
{"type": "minecraft:constant", "value": -1},
|
||||
{"type": "minecraft:dye", "default": -1},
|
||||
{"type": "computercraft:pocket_computer_light", "default": -15658735}
|
||||
]
|
||||
},
|
||||
"when": "blinking"
|
||||
}
|
||||
],
|
||||
"property": "computercraft:pocket_computer_state"
|
||||
},
|
||||
"property": "minecraft:has_component"
|
||||
}
|
||||
}
|
91
projects/common/src/generated/resources/assets/computercraft/items/pocket_computer_normal.json
generated
Normal file
91
projects/common/src/generated/resources/assets/computercraft/items/pocket_computer_normal.json
generated
Normal file
@@ -0,0 +1,91 @@
|
||||
{
|
||||
"model": {
|
||||
"type": "minecraft:condition",
|
||||
"component": "minecraft:dyed_color",
|
||||
"on_false": {
|
||||
"type": "minecraft:select",
|
||||
"cases": [
|
||||
{
|
||||
"model": {
|
||||
"type": "minecraft:model",
|
||||
"model": "computercraft:item/pocket_computer_normal",
|
||||
"tints": [
|
||||
{"type": "minecraft:constant", "value": -1},
|
||||
{"type": "minecraft:dye", "default": -1},
|
||||
{"type": "computercraft:pocket_computer_light", "default": -15658735}
|
||||
]
|
||||
},
|
||||
"when": "off"
|
||||
},
|
||||
{
|
||||
"model": {
|
||||
"type": "minecraft:model",
|
||||
"model": "computercraft:item/pocket_computer_normal_on",
|
||||
"tints": [
|
||||
{"type": "minecraft:constant", "value": -1},
|
||||
{"type": "minecraft:dye", "default": -1},
|
||||
{"type": "computercraft:pocket_computer_light", "default": -15658735}
|
||||
]
|
||||
},
|
||||
"when": "on"
|
||||
},
|
||||
{
|
||||
"model": {
|
||||
"type": "minecraft:model",
|
||||
"model": "computercraft:item/pocket_computer_normal_blinking",
|
||||
"tints": [
|
||||
{"type": "minecraft:constant", "value": -1},
|
||||
{"type": "minecraft:dye", "default": -1},
|
||||
{"type": "computercraft:pocket_computer_light", "default": -15658735}
|
||||
]
|
||||
},
|
||||
"when": "blinking"
|
||||
}
|
||||
],
|
||||
"property": "computercraft:pocket_computer_state"
|
||||
},
|
||||
"on_true": {
|
||||
"type": "minecraft:select",
|
||||
"cases": [
|
||||
{
|
||||
"model": {
|
||||
"type": "minecraft:model",
|
||||
"model": "computercraft:item/pocket_computer_colour",
|
||||
"tints": [
|
||||
{"type": "minecraft:constant", "value": -1},
|
||||
{"type": "minecraft:dye", "default": -1},
|
||||
{"type": "computercraft:pocket_computer_light", "default": -15658735}
|
||||
]
|
||||
},
|
||||
"when": "off"
|
||||
},
|
||||
{
|
||||
"model": {
|
||||
"type": "minecraft:model",
|
||||
"model": "computercraft:item/pocket_computer_colour_on",
|
||||
"tints": [
|
||||
{"type": "minecraft:constant", "value": -1},
|
||||
{"type": "minecraft:dye", "default": -1},
|
||||
{"type": "computercraft:pocket_computer_light", "default": -15658735}
|
||||
]
|
||||
},
|
||||
"when": "on"
|
||||
},
|
||||
{
|
||||
"model": {
|
||||
"type": "minecraft:model",
|
||||
"model": "computercraft:item/pocket_computer_colour_blinking",
|
||||
"tints": [
|
||||
{"type": "minecraft:constant", "value": -1},
|
||||
{"type": "minecraft:dye", "default": -1},
|
||||
{"type": "computercraft:pocket_computer_light", "default": -15658735}
|
||||
]
|
||||
},
|
||||
"when": "blinking"
|
||||
}
|
||||
],
|
||||
"property": "computercraft:pocket_computer_state"
|
||||
},
|
||||
"property": "minecraft:has_component"
|
||||
}
|
||||
}
|
1
projects/common/src/generated/resources/assets/computercraft/items/printed_book.json
generated
Normal file
1
projects/common/src/generated/resources/assets/computercraft/items/printed_book.json
generated
Normal file
@@ -0,0 +1 @@
|
||||
{"model": {"type": "minecraft:model", "model": "computercraft:item/printed_book"}}
|
1
projects/common/src/generated/resources/assets/computercraft/items/printed_page.json
generated
Normal file
1
projects/common/src/generated/resources/assets/computercraft/items/printed_page.json
generated
Normal file
@@ -0,0 +1 @@
|
||||
{"model": {"type": "minecraft:model", "model": "computercraft:item/printed_page"}}
|
1
projects/common/src/generated/resources/assets/computercraft/items/printed_pages.json
generated
Normal file
1
projects/common/src/generated/resources/assets/computercraft/items/printed_pages.json
generated
Normal file
@@ -0,0 +1 @@
|
||||
{"model": {"type": "minecraft:model", "model": "computercraft:item/printed_pages"}}
|
1
projects/common/src/generated/resources/assets/computercraft/items/printer.json
generated
Normal file
1
projects/common/src/generated/resources/assets/computercraft/items/printer.json
generated
Normal file
@@ -0,0 +1 @@
|
||||
{"model": {"type": "minecraft:model", "model": "computercraft:block/printer_empty"}}
|
1
projects/common/src/generated/resources/assets/computercraft/items/redstone_relay.json
generated
Normal file
1
projects/common/src/generated/resources/assets/computercraft/items/redstone_relay.json
generated
Normal file
@@ -0,0 +1 @@
|
||||
{"model": {"type": "minecraft:model", "model": "computercraft:block/redstone_relay"}}
|
1
projects/common/src/generated/resources/assets/computercraft/items/speaker.json
generated
Normal file
1
projects/common/src/generated/resources/assets/computercraft/items/speaker.json
generated
Normal file
@@ -0,0 +1 @@
|
||||
{"model": {"type": "minecraft:model", "model": "computercraft:block/speaker"}}
|
7
projects/common/src/generated/resources/assets/computercraft/items/treasure_disk.json
generated
Normal file
7
projects/common/src/generated/resources/assets/computercraft/items/treasure_disk.json
generated
Normal file
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"model": {
|
||||
"type": "minecraft:model",
|
||||
"model": "computercraft:item/treasure_disk",
|
||||
"tints": [{"type": "minecraft:constant", "value": -1}, {"type": "minecraft:dye", "default": -15658735}]
|
||||
}
|
||||
}
|
34
projects/common/src/generated/resources/assets/computercraft/items/turtle_advanced.json
generated
Normal file
34
projects/common/src/generated/resources/assets/computercraft/items/turtle_advanced.json
generated
Normal file
@@ -0,0 +1,34 @@
|
||||
{
|
||||
"model": {
|
||||
"type": "minecraft:composite",
|
||||
"models": [
|
||||
{
|
||||
"type": "minecraft:condition",
|
||||
"component": "minecraft:dyed_color",
|
||||
"on_false": {"type": "minecraft:model", "model": "computercraft:block/turtle_advanced"},
|
||||
"on_true": {"type": "minecraft:model", "model": "computercraft:block/turtle_colour"},
|
||||
"property": "minecraft:has_component"
|
||||
},
|
||||
{"type": "computercraft:turtle/upgrade", "side": "left", "transforms": "computercraft:block/turtle_advanced"},
|
||||
{"type": "computercraft:turtle/upgrade", "side": "right", "transforms": "computercraft:block/turtle_advanced"},
|
||||
{"type": "computercraft:turtle/overlay", "transforms": "computercraft:block/turtle_advanced"},
|
||||
{
|
||||
"type": "minecraft:select",
|
||||
"cases": [
|
||||
{
|
||||
"model": {
|
||||
"type": "minecraft:condition",
|
||||
"on_false": {"type": "minecraft:empty"},
|
||||
"on_true": {"type": "minecraft:model", "model": "computercraft:block/turtle_elf_overlay"},
|
||||
"property": "computercraft:turtle/show_elf_overlay"
|
||||
},
|
||||
"when": ["12-24", "12-25", "12-26"]
|
||||
}
|
||||
],
|
||||
"fallback": {"type": "minecraft:empty"},
|
||||
"pattern": "MM-dd",
|
||||
"property": "minecraft:local_time"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
34
projects/common/src/generated/resources/assets/computercraft/items/turtle_normal.json
generated
Normal file
34
projects/common/src/generated/resources/assets/computercraft/items/turtle_normal.json
generated
Normal file
@@ -0,0 +1,34 @@
|
||||
{
|
||||
"model": {
|
||||
"type": "minecraft:composite",
|
||||
"models": [
|
||||
{
|
||||
"type": "minecraft:condition",
|
||||
"component": "minecraft:dyed_color",
|
||||
"on_false": {"type": "minecraft:model", "model": "computercraft:block/turtle_normal"},
|
||||
"on_true": {"type": "minecraft:model", "model": "computercraft:block/turtle_colour"},
|
||||
"property": "minecraft:has_component"
|
||||
},
|
||||
{"type": "computercraft:turtle/upgrade", "side": "left", "transforms": "computercraft:block/turtle_normal"},
|
||||
{"type": "computercraft:turtle/upgrade", "side": "right", "transforms": "computercraft:block/turtle_normal"},
|
||||
{"type": "computercraft:turtle/overlay", "transforms": "computercraft:block/turtle_normal"},
|
||||
{
|
||||
"type": "minecraft:select",
|
||||
"cases": [
|
||||
{
|
||||
"model": {
|
||||
"type": "minecraft:condition",
|
||||
"on_false": {"type": "minecraft:empty"},
|
||||
"on_true": {"type": "minecraft:model", "model": "computercraft:block/turtle_elf_overlay"},
|
||||
"property": "computercraft:turtle/show_elf_overlay"
|
||||
},
|
||||
"when": ["12-24", "12-25", "12-26"]
|
||||
}
|
||||
],
|
||||
"fallback": {"type": "minecraft:empty"},
|
||||
"pattern": "MM-dd",
|
||||
"property": "minecraft:local_time"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
1
projects/common/src/generated/resources/assets/computercraft/items/wired_modem.json
generated
Normal file
1
projects/common/src/generated/resources/assets/computercraft/items/wired_modem.json
generated
Normal file
@@ -0,0 +1 @@
|
||||
{"model": {"type": "minecraft:model", "model": "computercraft:block/wired_modem_off"}}
|
1
projects/common/src/generated/resources/assets/computercraft/items/wired_modem_full.json
generated
Normal file
1
projects/common/src/generated/resources/assets/computercraft/items/wired_modem_full.json
generated
Normal file
@@ -0,0 +1 @@
|
||||
{"model": {"type": "minecraft:model", "model": "computercraft:block/wired_modem_full_off"}}
|
1
projects/common/src/generated/resources/assets/computercraft/items/wireless_modem_advanced.json
generated
Normal file
1
projects/common/src/generated/resources/assets/computercraft/items/wireless_modem_advanced.json
generated
Normal file
@@ -0,0 +1 @@
|
||||
{"model": {"type": "minecraft:model", "model": "computercraft:block/wireless_modem_advanced_off"}}
|
1
projects/common/src/generated/resources/assets/computercraft/items/wireless_modem_normal.json
generated
Normal file
1
projects/common/src/generated/resources/assets/computercraft/items/wireless_modem_normal.json
generated
Normal file
@@ -0,0 +1 @@
|
||||
{"model": {"type": "minecraft:model", "model": "computercraft:block/wireless_modem_normal_off"}}
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user