mirror of
https://github.com/SquidDev-CC/CC-Tweaked
synced 2025-07-07 12:32:54 +00:00
Update to 1.21.4
Please don't talk to me about this. The first couple of hours of this update were quite enjoyable, and then the rest was one of the most miserable times I've had modding. This has been a real slog, partly due to some large MC changes (item models are a great change, but a pain to adapt to), and partly due to mental health reasons — honestly, I've opened up my IDE so many times, and then just closed it because I've hated the thought of even working on this. I will publish this to my maven, so mod authors can depend on it, but I have no plans to publish a 1.21.4 version. 1.21.5 is right around the corner (again, with some cool, but no-doubt painful changes), and I need some time to focus on some breaking changes. This commit actually includes the 1.21.3 update — the git history got so messy here, so I just clobbered the whole thing. Sorry. == Rendering == - Remove TBO monitor renderer: There was a big overhaul to how shaders are defined and loaded in 1.21.2. It might have been possible to update the monitor shader code to this version, it doesn't see much use nowadays, so let's just delete it. This is a real shame — the TBO renderer was one of my favourite projects I've worked on. Unfortunately, it just doesn't seem worth the ongoing maintenance burden. It lives on in the standalone emulator :D. - Similarly, the VBO rendering code got a bit of an overhaul. We no longer use a custom VBO subclass, and instead just hack vanilla's to support changing the number of vertices rendered. This does mean we need to construct a MeshData, rather than a raw ByteBuffer. This isn't too hard, but not sure how it'll play with Iris. Given recent vanilla performance improvements, maybe we can remove our Unsafe code and use a normal BufferBuilder now. - Remove our custom emissive model code, now that vanilla supports it. We should add emissive textures to some other models at some point. - Remove mod-loader specific model code, and replace it with vanilla's ItemModel. This does constrain the design of turtle upgrade modellers quite a bit — we now only accept an untransformed BakedModel or a transformed ItemStack model. We may relax this in the future, unclear. This change does mean that updsidedown turtles are broken. RIP :(. - Entity rendering now separates reading state from the entity from actual rendering. This means we need to pass some extra state around for item frames. Easy on Forge, but requires a mixin on Fabric. == Recipes == There were several major changes to ingredients this update. The code here hasn't been very well tested right now — might be nice to add some game tests for this. - Ingredients can no longer be constructed directly from a tag key (it needs to be fetched from the current registries), so the recipe generation code needs a bit of a reshuffle. - DiskRecipe now accepts a custom list of ingredients, rather than being hard-coded (fixes #1755). Recipes can now return custom `RecipeDisplay`s used to show a recipe in the crafting book. We use this to replace the impostor recipes. I'm not entirely sure how well this'll play with other recipe mods. Here's hoping. - Similarly, our recipe mod integration has been updated to use RecipeDisplay. We had to do this as ingredients no longer accept arbitrary ItemStacks (only a specific item). == Misc == - Blocks/items now need to know their ID ahead of time (so they can compute their description). This requires some reshuffling to the registration code, but it's pretty minor. - updateShape and neighborChanged no longer take a direction (the Orientation is mostly null) and so invalidates all redstone and peripherals. - All the positions were lowered by one in game tests. It's a good change (they now match the positions in structures), but annoying to update for!
This commit is contained in:
parent
598fc4aefd
commit
9277aa33e9
@ -4,10 +4,7 @@
|
|||||||
|
|
||||||
/** Default configuration for Fabric projects. */
|
/** Default configuration for Fabric projects. */
|
||||||
|
|
||||||
import cc.tweaked.gradle.CCTweakedExtension
|
import cc.tweaked.gradle.*
|
||||||
import cc.tweaked.gradle.CCTweakedPlugin
|
|
||||||
import cc.tweaked.gradle.IdeaRunConfigurations
|
|
||||||
import cc.tweaked.gradle.MinecraftConfigurations
|
|
||||||
|
|
||||||
plugins {
|
plugins {
|
||||||
`java-library`
|
`java-library`
|
||||||
@ -67,3 +64,9 @@ dependencies {
|
|||||||
tasks.ideaSyncTask {
|
tasks.ideaSyncTask {
|
||||||
doLast { IdeaRunConfigurations(project).patch() }
|
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="LocalFinalVariableName" />
|
||||||
<module name="LocalVariableName" />
|
<module name="LocalVariableName" />
|
||||||
<module name="MemberName">
|
<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>
|
||||||
<module name="MethodName">
|
<module name="MethodName">
|
||||||
<property name="format" value="^(computercraft\$)?[a-z][a-zA-Z0-9]*$" />
|
<property name="format" value="^(computercraft\$)?[a-z][a-zA-Z0-9]*$" />
|
||||||
|
@ -15,4 +15,4 @@ isUnstable=true
|
|||||||
modVersion=1.115.1
|
modVersion=1.115.1
|
||||||
|
|
||||||
# Minecraft properties: We want to configure this here so we can read it in settings.gradle
|
# 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
|
# Minecraft
|
||||||
# MC version is specified in gradle.properties, as we need that in settings.gradle.
|
# 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
|
# Remember to update corresponding versions in fabric.mod.json/neoforge.mods.toml
|
||||||
fabric-api = "0.102.1+1.21.1"
|
fabric-api = "0.118.0+1.21.4"
|
||||||
fabric-loader = "0.15.11"
|
fabric-loader = "0.16.10"
|
||||||
neoForge = "21.1.9"
|
neoForge = "21.4.101-beta"
|
||||||
neoForgeSpi = "8.0.1"
|
neoForgeSpi = "8.0.1"
|
||||||
mixin = "0.8.5"
|
mixin = "0.8.5"
|
||||||
parchment = "2024.07.28"
|
parchment = "2024.12.07"
|
||||||
parchmentMc = "1.21"
|
parchmentMc = "1.21.4"
|
||||||
yarn = "1.21.1+build.1"
|
yarn = "1.21.4+build.1"
|
||||||
|
|
||||||
# Core dependencies (these versions are tied to the version Minecraft uses)
|
# Core dependencies (these versions are tied to the version Minecraft uses)
|
||||||
fastutil = "8.5.12"
|
fastutil = "8.5.15"
|
||||||
guava = "32.1.2-jre"
|
guava = "33.3.1-jre"
|
||||||
netty = "4.1.97.Final"
|
netty = "4.1.115.Final"
|
||||||
slf4j = "2.0.9"
|
slf4j = "2.0.16"
|
||||||
|
|
||||||
# Core dependencies (independent of Minecraft)
|
# Core dependencies (independent of Minecraft)
|
||||||
asm = "9.6"
|
asm = "9.7.1"
|
||||||
autoService = "1.1.1"
|
autoService = "1.1.1"
|
||||||
checkerFramework = "3.42.0"
|
checkerFramework = "3.42.0"
|
||||||
cobalt = { strictly = "0.9.5" }
|
cobalt = { strictly = "0.9.5" }
|
||||||
@ -37,15 +37,15 @@ nightConfig = "3.8.1"
|
|||||||
|
|
||||||
# Minecraft mods
|
# Minecraft mods
|
||||||
emi = "1.1.7+1.21"
|
emi = "1.1.7+1.21"
|
||||||
fabricPermissions = "0.3.1"
|
fabricPermissions = "0.3.3"
|
||||||
iris-fabric = "1.8.0-beta.3+1.21-fabric"
|
iris-fabric = "1.8.8+1.21.4-fabric"
|
||||||
iris-forge = "1.8.0-beta.3+1.21-neoforge"
|
iris-forge = "1.8.8+1.21.4-neoforge"
|
||||||
jei = "19.8.2.99"
|
jei = "19.8.2.99"
|
||||||
modmenu = "11.0.0-rc.4"
|
modmenu = "13.0.2"
|
||||||
moreRed = "6.0.0.3"
|
moreRed = "6.0.0.3"
|
||||||
rei = "16.0.729"
|
rei = "18.0.800"
|
||||||
sodium-fabric = "mc1.21-0.6.0-beta.1-fabric"
|
sodium-fabric = "mc1.21.4-0.6.10-fabric"
|
||||||
sodium-forge = "mc1.21-0.6.0-beta.1-neoforge"
|
sodium-forge = "mc1.21.4-0.6.10-neoforge"
|
||||||
mixinExtra = "0.3.5"
|
mixinExtra = "0.3.5"
|
||||||
create-forge = "6.0.0-6"
|
create-forge = "6.0.0-6"
|
||||||
create-fabric = "0.5.1-f-build.1467+mc1.20.1"
|
create-fabric = "0.5.1-f-build.1467+mc1.20.1"
|
||||||
@ -186,9 +186,9 @@ kotlin = ["kotlin-stdlib", "kotlin-coroutines"]
|
|||||||
# Minecraft
|
# Minecraft
|
||||||
externalMods-common = ["iris-forge", "jei-api", "nightConfig-core", "nightConfig-toml"]
|
externalMods-common = ["iris-forge", "jei-api", "nightConfig-core", "nightConfig-toml"]
|
||||||
externalMods-forge-compile = ["moreRed", "iris-forge", "jei-api"]
|
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-compile = ["fabricPermissions", "iris-fabric", "jei-api", "rei-api", "rei-builtin"]
|
||||||
externalMods-fabric-runtime = ["jei-fabric", "modmenu"]
|
externalMods-fabric-runtime = []
|
||||||
|
|
||||||
# Testing
|
# Testing
|
||||||
test = ["junit-jupiter-api", "junit-jupiter-params", "hamcrest", "jqwik-api"]
|
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 dan200.computercraft.impl.client.ClientPlatformHelper;
|
||||||
import net.minecraft.client.Minecraft;
|
import net.minecraft.client.Minecraft;
|
||||||
import net.minecraft.client.resources.model.BakedModel;
|
import net.minecraft.client.resources.model.BakedModel;
|
||||||
import net.minecraft.client.resources.model.ModelResourceLocation;
|
|
||||||
import net.minecraft.resources.ResourceLocation;
|
import net.minecraft.resources.ResourceLocation;
|
||||||
import net.minecraft.world.item.ItemStack;
|
import net.minecraft.world.item.ItemStack;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A model to render, combined with a transformation matrix to apply.
|
* 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 sealed interface TransformedModel permits TransformedModel.Baked, TransformedModel.Item {
|
||||||
public TransformedModel(BakedModel model) {
|
record Baked(BakedModel model) implements TransformedModel {
|
||||||
this(model, Transformation.identity());
|
}
|
||||||
|
|
||||||
|
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.
|
* @param location The location of the model to load.
|
||||||
* @return The new {@link TransformedModel} instance.
|
* @return The new {@link TransformedModel} instance.
|
||||||
*/
|
*/
|
||||||
public static TransformedModel of(ModelLocation location) {
|
static TransformedModel of(ResourceLocation location) {
|
||||||
var modelManager = Minecraft.getInstance().getModelManager();
|
var modelManager = Minecraft.getInstance().getModelManager();
|
||||||
return new TransformedModel(location.getModel(modelManager));
|
return of(ClientPlatformHelper.get().getModel(modelManager, location));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
static TransformedModel of(ItemStack item, Transformation transform) {
|
||||||
* Look up a model in the model bakery and construct a {@link TransformedModel} with no transformation.
|
return new TransformedModel.Item(item, transform);
|
||||||
*
|
|
||||||
* @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);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -4,7 +4,6 @@
|
|||||||
|
|
||||||
package dan200.computercraft.api.client.turtle;
|
package dan200.computercraft.api.client.turtle;
|
||||||
|
|
||||||
import dan200.computercraft.api.client.ModelLocation;
|
|
||||||
import dan200.computercraft.api.client.TransformedModel;
|
import dan200.computercraft.api.client.TransformedModel;
|
||||||
import dan200.computercraft.api.turtle.ITurtleAccess;
|
import dan200.computercraft.api.turtle.ITurtleAccess;
|
||||||
import dan200.computercraft.api.turtle.ITurtleUpgrade;
|
import dan200.computercraft.api.turtle.ITurtleUpgrade;
|
||||||
@ -55,7 +54,7 @@ public interface TurtleUpgradeModeller<T extends ITurtleUpgrade> {
|
|||||||
* by other means.
|
* by other means.
|
||||||
*
|
*
|
||||||
* @return A list of models that this modeller depends on.
|
* @return A list of models that this modeller depends on.
|
||||||
* @see UnbakedModel#getDependencies()
|
* @see UnbakedModel#resolveDependencies(UnbakedModel.Resolver)
|
||||||
*/
|
*/
|
||||||
default Stream<ResourceLocation> getDependencies() {
|
default Stream<ResourceLocation> getDependencies() {
|
||||||
return Stream.of();
|
return Stream.of();
|
||||||
@ -85,18 +84,6 @@ public interface TurtleUpgradeModeller<T extends ITurtleUpgrade> {
|
|||||||
* @return The constructed modeller.
|
* @return The constructed modeller.
|
||||||
*/
|
*/
|
||||||
static <T extends ITurtleUpgrade> TurtleUpgradeModeller<T> sided(ResourceLocation left, ResourceLocation right) {
|
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<>() {
|
return new TurtleUpgradeModeller<>() {
|
||||||
@Override
|
@Override
|
||||||
public TransformedModel getModel(T upgrade, @Nullable ITurtleAccess turtle, TurtleSide side, DataComponentPatch data) {
|
public TransformedModel getModel(T upgrade, @Nullable ITurtleAccess turtle, TurtleSide side, DataComponentPatch data) {
|
||||||
@ -105,7 +92,7 @@ public interface TurtleUpgradeModeller<T extends ITurtleUpgrade> {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Stream<ResourceLocation> getDependencies() {
|
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;
|
package dan200.computercraft.api.client.turtle;
|
||||||
|
|
||||||
|
import com.mojang.math.Axis;
|
||||||
import com.mojang.math.Transformation;
|
import com.mojang.math.Transformation;
|
||||||
import dan200.computercraft.api.client.TransformedModel;
|
import dan200.computercraft.api.client.TransformedModel;
|
||||||
import dan200.computercraft.api.turtle.ITurtleAccess;
|
import dan200.computercraft.api.turtle.ITurtleAccess;
|
||||||
import dan200.computercraft.api.turtle.ITurtleUpgrade;
|
import dan200.computercraft.api.turtle.ITurtleUpgrade;
|
||||||
import dan200.computercraft.api.turtle.TurtleSide;
|
import dan200.computercraft.api.turtle.TurtleSide;
|
||||||
import dan200.computercraft.impl.client.ClientPlatformHelper;
|
|
||||||
import net.minecraft.client.Minecraft;
|
|
||||||
import net.minecraft.core.component.DataComponentPatch;
|
import net.minecraft.core.component.DataComponentPatch;
|
||||||
import org.joml.Matrix4f;
|
import org.joml.Matrix4f;
|
||||||
import org.jspecify.annotations.Nullable;
|
import org.jspecify.annotations.Nullable;
|
||||||
|
|
||||||
final class TurtleUpgradeModellers {
|
final class TurtleUpgradeModellers {
|
||||||
private static final Transformation leftTransform = getMatrixFor(-0.4065f);
|
private static final Transformation leftTransform = getMatrixFor(TurtleSide.LEFT);
|
||||||
private static final Transformation rightTransform = getMatrixFor(0.4065f);
|
private static final Transformation rightTransform = getMatrixFor(TurtleSide.RIGHT);
|
||||||
|
|
||||||
private static Transformation getMatrixFor(float offset) {
|
private static Transformation getMatrixFor(TurtleSide side) {
|
||||||
var matrix = new Matrix4f();
|
var pose = new Matrix4f();
|
||||||
matrix.set(new float[]{
|
pose.translate(0.5f, 0.5f, 0.5f);
|
||||||
0.0f, 0.0f, -1.0f, 1.0f + offset,
|
pose.rotate(Axis.YN.rotationDegrees(90f));
|
||||||
1.0f, 0.0f, 0.0f, 0.0f,
|
pose.rotate(Axis.ZP.rotationDegrees(90f));
|
||||||
0.0f, -1.0f, 0.0f, 1.0f,
|
pose.translate(0.0f, 0.0f, side == TurtleSide.RIGHT ? -0.4065f : 0.4065f);
|
||||||
0.0f, 0.0f, 0.0f, 1.0f,
|
return new Transformation(pose);
|
||||||
});
|
|
||||||
matrix.transpose();
|
|
||||||
return new Transformation(matrix);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static final TurtleUpgradeModeller<ITurtleUpgrade> UPGRADE_ITEM = new UpgradeItemModeller();
|
static final TurtleUpgradeModeller<ITurtleUpgrade> UPGRADE_ITEM = new UpgradeItemModeller();
|
||||||
@ -36,10 +32,7 @@ final class TurtleUpgradeModellers {
|
|||||||
private static final class UpgradeItemModeller implements TurtleUpgradeModeller<ITurtleUpgrade> {
|
private static final class UpgradeItemModeller implements TurtleUpgradeModeller<ITurtleUpgrade> {
|
||||||
@Override
|
@Override
|
||||||
public TransformedModel getModel(ITurtleUpgrade upgrade, @Nullable ITurtleAccess turtle, TurtleSide side, DataComponentPatch data) {
|
public TransformedModel getModel(ITurtleUpgrade upgrade, @Nullable ITurtleAccess turtle, TurtleSide side, DataComponentPatch data) {
|
||||||
var stack = upgrade.getUpgradeItem(data);
|
return TransformedModel.of(upgrade.getUpgradeItem(data), side == TurtleSide.LEFT ? leftTransform : rightTransform);
|
||||||
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);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -4,12 +4,9 @@
|
|||||||
|
|
||||||
package dan200.computercraft.impl.client;
|
package dan200.computercraft.impl.client;
|
||||||
|
|
||||||
import dan200.computercraft.api.client.ModelLocation;
|
|
||||||
import dan200.computercraft.impl.Services;
|
import dan200.computercraft.impl.Services;
|
||||||
import net.minecraft.client.renderer.RenderType;
|
|
||||||
import net.minecraft.client.resources.model.BakedModel;
|
import net.minecraft.client.resources.model.BakedModel;
|
||||||
import net.minecraft.client.resources.model.ModelManager;
|
import net.minecraft.client.resources.model.ModelManager;
|
||||||
import net.minecraft.client.resources.model.ModelResourceLocation;
|
|
||||||
import net.minecraft.resources.ResourceLocation;
|
import net.minecraft.resources.ResourceLocation;
|
||||||
import org.jetbrains.annotations.ApiStatus;
|
import org.jetbrains.annotations.ApiStatus;
|
||||||
import org.jspecify.annotations.Nullable;
|
import org.jspecify.annotations.Nullable;
|
||||||
@ -22,33 +19,9 @@ public interface ClientPlatformHelper {
|
|||||||
* @param manager The model manager.
|
* @param manager The model manager.
|
||||||
* @param resourceLocation The model resourceLocation.
|
* @param resourceLocation The model resourceLocation.
|
||||||
* @return The baked model.
|
* @return The baked model.
|
||||||
* @see ModelLocation
|
|
||||||
*/
|
*/
|
||||||
BakedModel getModel(ModelManager manager, ResourceLocation resourceLocation);
|
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() {
|
static ClientPlatformHelper get() {
|
||||||
var instance = Instance.INSTANCE;
|
var instance = Instance.INSTANCE;
|
||||||
return instance == null ? Services.raise(ClientPlatformHelper.class, Instance.ERROR) : 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.IComputerSystem;
|
||||||
import dan200.computercraft.api.lua.ILuaAPI;
|
import dan200.computercraft.api.lua.ILuaAPI;
|
||||||
import dan200.computercraft.api.lua.ILuaAPIFactory;
|
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.PacketNetwork;
|
||||||
import dan200.computercraft.api.network.wired.WiredElement;
|
import dan200.computercraft.api.network.wired.WiredElement;
|
||||||
import dan200.computercraft.api.network.wired.WiredNode;
|
import dan200.computercraft.api.network.wired.WiredNode;
|
||||||
@ -142,19 +140,6 @@ public final class ComputerCraftAPI {
|
|||||||
return getInstance().getBundledRedstoneOutput(world, pos, side);
|
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.
|
* 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;
|
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.
|
* 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).
|
* 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).
|
* 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 ITurtleUpgrade
|
||||||
* @see IPocketUpgrade
|
* @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.
|
* 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.filesystem.WritableMount;
|
||||||
import dan200.computercraft.api.lua.GenericSource;
|
import dan200.computercraft.api.lua.GenericSource;
|
||||||
import dan200.computercraft.api.lua.ILuaAPIFactory;
|
import dan200.computercraft.api.lua.ILuaAPIFactory;
|
||||||
import dan200.computercraft.api.media.MediaProvider;
|
|
||||||
import dan200.computercraft.api.media.PrintoutContents;
|
import dan200.computercraft.api.media.PrintoutContents;
|
||||||
import dan200.computercraft.api.network.PacketNetwork;
|
import dan200.computercraft.api.network.PacketNetwork;
|
||||||
import dan200.computercraft.api.network.wired.WiredElement;
|
import dan200.computercraft.api.network.wired.WiredElement;
|
||||||
@ -60,8 +59,6 @@ public interface ComputerCraftAPIService {
|
|||||||
|
|
||||||
int getBundledRedstoneOutput(Level world, BlockPos pos, Direction side);
|
int getBundledRedstoneOutput(Level world, BlockPos pos, Direction side);
|
||||||
|
|
||||||
void registerMediaProvider(MediaProvider provider);
|
|
||||||
|
|
||||||
PacketNetwork getWirelessNetwork(MinecraftServer server);
|
PacketNetwork getWirelessNetwork(MinecraftServer server);
|
||||||
|
|
||||||
void registerAPIFactory(ILuaAPIFactory factory);
|
void registerAPIFactory(ILuaAPIFactory factory);
|
||||||
|
@ -11,6 +11,13 @@ plugins {
|
|||||||
id("cc-tweaked.publishing")
|
id("cc-tweaked.publishing")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
sourceSets.client {
|
||||||
|
java {
|
||||||
|
exclude("dan200/computercraft/client/integration/emi")
|
||||||
|
exclude("dan200/computercraft/client/integration/jei")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
minecraft {
|
minecraft {
|
||||||
accessWideners(
|
accessWideners(
|
||||||
"src/main/resources/computercraft.accesswidener",
|
"src/main/resources/computercraft.accesswidener",
|
||||||
|
@ -6,12 +6,13 @@ package dan200.computercraft.client;
|
|||||||
|
|
||||||
import com.mojang.blaze3d.audio.Channel;
|
import com.mojang.blaze3d.audio.Channel;
|
||||||
import com.mojang.blaze3d.vertex.PoseStack;
|
import com.mojang.blaze3d.vertex.PoseStack;
|
||||||
|
import com.mojang.math.Axis;
|
||||||
import dan200.computercraft.api.turtle.TurtleSide;
|
import dan200.computercraft.api.turtle.TurtleSide;
|
||||||
import dan200.computercraft.client.pocket.ClientPocketComputers;
|
import dan200.computercraft.client.pocket.ClientPocketComputers;
|
||||||
import dan200.computercraft.client.render.CableHighlightRenderer;
|
import dan200.computercraft.client.render.CableHighlightRenderer;
|
||||||
|
import dan200.computercraft.client.render.ExtendedItemFrameRenderState;
|
||||||
import dan200.computercraft.client.render.PocketItemRenderer;
|
import dan200.computercraft.client.render.PocketItemRenderer;
|
||||||
import dan200.computercraft.client.render.PrintoutItemRenderer;
|
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.MonitorHighlightRenderer;
|
||||||
import dan200.computercraft.client.render.monitor.MonitorRenderState;
|
import dan200.computercraft.client.render.monitor.MonitorRenderState;
|
||||||
import dan200.computercraft.client.sound.SpeakerManager;
|
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.Camera;
|
||||||
import net.minecraft.client.Minecraft;
|
import net.minecraft.client.Minecraft;
|
||||||
import net.minecraft.client.renderer.MultiBufferSource;
|
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.AudioStream;
|
||||||
import net.minecraft.client.sounds.SoundEngine;
|
import net.minecraft.client.sounds.SoundEngine;
|
||||||
import net.minecraft.core.BlockPos;
|
import net.minecraft.core.BlockPos;
|
||||||
import net.minecraft.world.InteractionHand;
|
import net.minecraft.world.InteractionHand;
|
||||||
import net.minecraft.world.entity.decoration.ItemFrame;
|
|
||||||
import net.minecraft.world.item.ItemStack;
|
import net.minecraft.world.item.ItemStack;
|
||||||
import net.minecraft.world.level.block.state.BlockState;
|
import net.minecraft.world.level.block.state.BlockState;
|
||||||
import net.minecraft.world.phys.BlockHitResult;
|
import net.minecraft.world.phys.BlockHitResult;
|
||||||
@ -91,9 +92,10 @@ public final class ClientHooks {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static boolean onRenderItemFrame(PoseStack transform, MultiBufferSource render, ItemFrame frame, ItemStack stack, int light) {
|
public static boolean onRenderItemFrame(PoseStack transform, MultiBufferSource render, ItemFrameRenderState frame, ExtendedItemFrameRenderState state, int light) {
|
||||||
if (stack.getItem() instanceof PrintoutItem) {
|
if (state.printoutData != null) {
|
||||||
PrintoutItemRenderer.onRenderInFrame(transform, render, frame, stack, light);
|
transform.mulPose(Axis.ZP.rotationDegrees(frame.rotation * 360.0f / 8.0f));
|
||||||
|
PrintoutItemRenderer.onRenderInFrame(transform, render, frame, state.printoutData, state.isBook, light);
|
||||||
return true;
|
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()));
|
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) {
|
public static @Nullable BlockState getBlockBreakingState(BlockState state, BlockPos pos) {
|
||||||
// Only apply to cables which have both a cable and modem
|
// Only apply to cables which have both a cable and modem
|
||||||
if (state.getBlock() != ModRegistry.Blocks.CABLE.get()
|
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.arguments.IntegerArgumentType;
|
||||||
import com.mojang.brigadier.builder.LiteralArgumentBuilder;
|
import com.mojang.brigadier.builder.LiteralArgumentBuilder;
|
||||||
import com.mojang.brigadier.builder.RequiredArgumentBuilder;
|
import com.mojang.brigadier.builder.RequiredArgumentBuilder;
|
||||||
|
import com.mojang.serialization.MapCodec;
|
||||||
import dan200.computercraft.api.ComputerCraftAPI;
|
import dan200.computercraft.api.ComputerCraftAPI;
|
||||||
import dan200.computercraft.api.client.turtle.RegisterTurtleUpgradeModeller;
|
import dan200.computercraft.api.client.turtle.RegisterTurtleUpgradeModeller;
|
||||||
import dan200.computercraft.api.client.turtle.TurtleUpgradeModeller;
|
import dan200.computercraft.api.client.turtle.TurtleUpgradeModeller;
|
||||||
import dan200.computercraft.client.gui.*;
|
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.CustomLecternRenderer;
|
||||||
import dan200.computercraft.client.render.RenderTypes;
|
|
||||||
import dan200.computercraft.client.render.TurtleBlockEntityRenderer;
|
import dan200.computercraft.client.render.TurtleBlockEntityRenderer;
|
||||||
import dan200.computercraft.client.render.monitor.MonitorBlockEntityRenderer;
|
import dan200.computercraft.client.render.monitor.MonitorBlockEntityRenderer;
|
||||||
import dan200.computercraft.client.turtle.TurtleModemModeller;
|
import dan200.computercraft.client.turtle.TurtleModemModeller;
|
||||||
import dan200.computercraft.client.turtle.TurtleUpgradeModellers;
|
import dan200.computercraft.client.turtle.TurtleUpgradeModellers;
|
||||||
import dan200.computercraft.core.util.Colour;
|
|
||||||
import dan200.computercraft.shared.ModRegistry;
|
import dan200.computercraft.shared.ModRegistry;
|
||||||
import dan200.computercraft.shared.command.CommandComputerCraft;
|
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.core.ServerContext;
|
||||||
import dan200.computercraft.shared.computer.inventory.AbstractComputerMenu;
|
import dan200.computercraft.shared.computer.inventory.AbstractComputerMenu;
|
||||||
import dan200.computercraft.shared.media.items.DiskItem;
|
|
||||||
import dan200.computercraft.shared.turtle.TurtleOverlay;
|
import dan200.computercraft.shared.turtle.TurtleOverlay;
|
||||||
import net.minecraft.Util;
|
import net.minecraft.Util;
|
||||||
import net.minecraft.client.Minecraft;
|
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.MenuScreens;
|
||||||
import net.minecraft.client.gui.screens.Screen;
|
import net.minecraft.client.gui.screens.Screen;
|
||||||
import net.minecraft.client.gui.screens.inventory.MenuAccess;
|
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.BlockEntityRendererProvider;
|
||||||
import net.minecraft.client.renderer.blockentity.BlockEntityRenderers;
|
import net.minecraft.client.renderer.blockentity.BlockEntityRenderers;
|
||||||
import net.minecraft.client.renderer.item.ClampedItemPropertyFunction;
|
import net.minecraft.client.renderer.item.ItemModel;
|
||||||
import net.minecraft.client.renderer.item.ItemProperties;
|
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.network.chat.Component;
|
||||||
import net.minecraft.resources.ResourceLocation;
|
import net.minecraft.resources.ResourceLocation;
|
||||||
import net.minecraft.server.packs.resources.PreparableReloadListener;
|
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.AbstractContainerMenu;
|
||||||
import net.minecraft.world.inventory.MenuType;
|
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.File;
|
||||||
import java.io.IOException;
|
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
import java.util.function.BiConsumer;
|
import java.util.function.BiConsumer;
|
||||||
import java.util.function.Consumer;
|
import java.util.function.Consumer;
|
||||||
import java.util.function.Supplier;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Registers client-side objects, such as {@link BlockEntityRendererProvider}s and
|
* 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);
|
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) {
|
public static void registerMenuScreens(RegisterMenuScreen register) {
|
||||||
register.<AbstractComputerMenu, ComputerScreen<AbstractComputerMenu>>register(ModRegistry.Menus.COMPUTER.get(), ComputerScreen::new);
|
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);
|
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());
|
register.register(ModRegistry.TurtleUpgradeTypes.TOOL.get(), TurtleUpgradeModeller.flatItem());
|
||||||
}
|
}
|
||||||
|
|
||||||
@SafeVarargs
|
public static void registerReloadListeners(BiConsumer<ResourceLocation, PreparableReloadListener> register, Minecraft minecraft) {
|
||||||
private static void registerItemProperty(RegisterItemProperty itemProperties, String name, ClampedItemPropertyFunction getter, Supplier<? extends Item>... items) {
|
register.accept(ResourceLocation.fromNamespaceAndPath(ComputerCraftAPI.MOD_ID, "sprites"), GuiSprites.initialise(minecraft.getTextureManager()));
|
||||||
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()));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private static final ResourceLocation[] EXTRA_MODELS = {
|
private static final ResourceLocation[] EXTRA_MODELS = {
|
||||||
TurtleOverlay.ELF_MODEL,
|
TurtleOverlay.ELF_MODEL,
|
||||||
|
TurtleBlockEntityRenderer.NORMAL_TURTLE_MODEL,
|
||||||
|
TurtleBlockEntityRenderer.ADVANCED_TURTLE_MODEL,
|
||||||
TurtleBlockEntityRenderer.COLOUR_TURTLE_MODEL,
|
TurtleBlockEntityRenderer.COLOUR_TURTLE_MODEL,
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -158,56 +117,21 @@ public final class ClientRegistry {
|
|||||||
TurtleUpgradeModellers.getDependencies().forEach(register);
|
TurtleUpgradeModellers.getDependencies().forEach(register);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void registerItemColours(BiConsumer<ItemColor, ItemLike> register) {
|
public static void registerItemModels(BiConsumer<ResourceLocation, MapCodec<? extends ItemModel.Unbaked>> register) {
|
||||||
register.accept(
|
register.accept(TurtleOverlayModel.ID, TurtleOverlayModel.CODEC);
|
||||||
(stack, layer) -> layer == 1 ? DiskItem.getColour(stack) : -1,
|
register.accept(TurtleUpgradeModel.ID, TurtleUpgradeModel.CODEC);
|
||||||
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());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private static int getPocketColour(ItemStack stack, int layer) {
|
public static void registerItemColours(BiConsumer<ResourceLocation, MapCodec<? extends ItemTintSource>> register) {
|
||||||
return switch (layer) {
|
register.accept(PocketComputerLight.ID, PocketComputerLight.CODEC);
|
||||||
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());
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private static int getTurtleColour(ItemStack stack, int layer) {
|
public static void registerSelectItemProperties(BiConsumer<ResourceLocation, SelectItemModelProperty.Type<?, ?>> register) {
|
||||||
return layer == 0 ? DyedItemColor.getOrDefault(stack, -1) : -1;
|
register.accept(PocketComputerStateProperty.ID, PocketComputerStateProperty.TYPE);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void registerShaders(ResourceProvider resources, BiConsumer<ShaderInstance, Consumer<ShaderInstance>> load) throws IOException {
|
public static void registerConditionalItemProperties(BiConsumer<ResourceLocation, MapCodec<? extends ConditionalItemModelProperty>> register) {
|
||||||
RenderTypes.registerShaders(resources, load);
|
register.accept(TurtleShowElfOverlay.ID, TurtleShowElfOverlay.CODEC);
|
||||||
}
|
|
||||||
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -99,7 +99,7 @@ public abstract class AbstractComputerScreen<T extends AbstractComputerMenu> ext
|
|||||||
|
|
||||||
if (uploadNagDeadline != Long.MAX_VALUE && Util.getNanos() >= uploadNagDeadline) {
|
if (uploadNagDeadline != Long.MAX_VALUE && Util.getNanos() >= uploadNagDeadline) {
|
||||||
new ItemToast(minecraft(), displayStack, NO_RESPONSE_TITLE, NO_RESPONSE_MSG, ItemToast.TRANSFER_NO_RESPONSE_TOKEN)
|
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;
|
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.ComputerSidebar;
|
||||||
import dan200.computercraft.client.gui.widgets.TerminalWidget;
|
import dan200.computercraft.client.gui.widgets.TerminalWidget;
|
||||||
import dan200.computercraft.client.render.ComputerBorderRenderer;
|
import dan200.computercraft.client.render.ComputerBorderRenderer;
|
||||||
import dan200.computercraft.client.render.RenderTypes;
|
|
||||||
import dan200.computercraft.client.render.SpriteRenderer;
|
import dan200.computercraft.client.render.SpriteRenderer;
|
||||||
import dan200.computercraft.shared.computer.inventory.AbstractComputerMenu;
|
import dan200.computercraft.shared.computer.inventory.AbstractComputerMenu;
|
||||||
import net.minecraft.client.gui.GuiGraphics;
|
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) {
|
public void renderBg(GuiGraphics graphics, float partialTicks, int mouseX, int mouseY) {
|
||||||
// Draw a border around the terminal
|
// Draw a border around the terminal
|
||||||
var terminal = getTerminal();
|
var terminal = getTerminal();
|
||||||
var spriteRenderer = SpriteRenderer.createForGui(graphics, RenderTypes.GUI_SPRITES);
|
|
||||||
var computerTextures = GuiSprites.getComputerTextures(family);
|
|
||||||
|
|
||||||
ComputerBorderRenderer.render(
|
SpriteRenderer.inGui(graphics, spriteRenderer -> {
|
||||||
spriteRenderer, computerTextures,
|
var computerTextures = GuiSprites.getComputerTextures(family);
|
||||||
terminal.getX(), terminal.getY(), terminal.getWidth(), terminal.getHeight(), false
|
ComputerBorderRenderer.render(
|
||||||
);
|
spriteRenderer, computerTextures,
|
||||||
ComputerSidebar.renderBackground(spriteRenderer, computerTextures, leftPos, topPos + sidebarYOffset);
|
terminal.getX(), terminal.getY(), terminal.getWidth(), terminal.getHeight(), false
|
||||||
graphics.flush(); // Flush to ensure background textures are drawn before foreground.
|
);
|
||||||
|
ComputerSidebar.renderBackground(spriteRenderer, computerTextures, leftPos, topPos + sidebarYOffset);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -7,6 +7,7 @@ package dan200.computercraft.client.gui;
|
|||||||
import dan200.computercraft.shared.peripheral.diskdrive.DiskDriveMenu;
|
import dan200.computercraft.shared.peripheral.diskdrive.DiskDriveMenu;
|
||||||
import net.minecraft.client.gui.GuiGraphics;
|
import net.minecraft.client.gui.GuiGraphics;
|
||||||
import net.minecraft.client.gui.screens.inventory.AbstractContainerScreen;
|
import net.minecraft.client.gui.screens.inventory.AbstractContainerScreen;
|
||||||
|
import net.minecraft.client.renderer.RenderType;
|
||||||
import net.minecraft.network.chat.Component;
|
import net.minecraft.network.chat.Component;
|
||||||
import net.minecraft.resources.ResourceLocation;
|
import net.minecraft.resources.ResourceLocation;
|
||||||
import net.minecraft.world.entity.player.Inventory;
|
import net.minecraft.world.entity.player.Inventory;
|
||||||
@ -23,7 +24,7 @@ public class DiskDriveScreen extends AbstractContainerScreen<DiskDriveMenu> {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void renderBg(GuiGraphics graphics, float partialTicks, int mouseX, int mouseY) {
|
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
|
@Override
|
||||||
|
@ -5,9 +5,11 @@
|
|||||||
package dan200.computercraft.client.gui;
|
package dan200.computercraft.client.gui;
|
||||||
|
|
||||||
import net.minecraft.client.Minecraft;
|
import net.minecraft.client.Minecraft;
|
||||||
|
import net.minecraft.client.gui.Font;
|
||||||
import net.minecraft.client.gui.GuiGraphics;
|
import net.minecraft.client.gui.GuiGraphics;
|
||||||
import net.minecraft.client.gui.components.toasts.Toast;
|
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.network.chat.Component;
|
||||||
import net.minecraft.resources.ResourceLocation;
|
import net.minecraft.resources.ResourceLocation;
|
||||||
import net.minecraft.util.FormattedCharSequence;
|
import net.minecraft.util.FormattedCharSequence;
|
||||||
@ -35,8 +37,9 @@ public class ItemToast implements Toast {
|
|||||||
private final Object token;
|
private final Object token;
|
||||||
private final int width;
|
private final int width;
|
||||||
|
|
||||||
private boolean isNew = true;
|
private boolean changed = true;
|
||||||
private long firstDisplay;
|
private long lastChanged;
|
||||||
|
private Visibility visibility = Visibility.HIDE;
|
||||||
|
|
||||||
public ItemToast(Minecraft minecraft, ItemStack stack, Component title, Component message, Object token) {
|
public ItemToast(Minecraft minecraft, ItemStack stack, Component title, Component message, Object token) {
|
||||||
this.stack = stack;
|
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;
|
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());
|
var existing = toasts.getToast(ItemToast.class, getToken());
|
||||||
if (existing != null) {
|
if (existing != null) {
|
||||||
existing.isNew = true;
|
existing.changed = true;
|
||||||
} else {
|
} else {
|
||||||
toasts.addToast(this);
|
toasts.addToast(this);
|
||||||
}
|
}
|
||||||
@ -73,28 +76,22 @@ public class ItemToast implements Toast {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Visibility render(GuiGraphics graphics, ToastComponent component, long time) {
|
public Visibility getWantedVisibility() {
|
||||||
if (isNew) {
|
return visibility;
|
||||||
|
}
|
||||||
|
|
||||||
firstDisplay = time;
|
@Override
|
||||||
isNew = false;
|
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) {
|
@Override
|
||||||
graphics.blitSprite(TEXTURE, 0, 0, width, height());
|
public void render(GuiGraphics graphics, Font font, long time) {
|
||||||
} else {
|
graphics.blitSprite(RenderType::guiTextured, TEXTURE, 0, 0, width(), height());
|
||||||
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
|
|
||||||
var textX = MARGIN;
|
var textX = MARGIN;
|
||||||
if (!stack.isEmpty()) {
|
if (!stack.isEmpty()) {
|
||||||
@ -102,23 +99,9 @@ public class ItemToast implements Toast {
|
|||||||
graphics.renderFakeItem(stack, MARGIN, MARGIN + height() / 2 - IMAGE_SIZE);
|
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) {
|
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 dan200.computercraft.shared.computer.inventory.AbstractComputerMenu;
|
||||||
import net.minecraft.client.KeyMapping;
|
import net.minecraft.client.KeyMapping;
|
||||||
import net.minecraft.client.Minecraft;
|
import net.minecraft.client.Minecraft;
|
||||||
|
import net.minecraft.client.ScrollWheelHandler;
|
||||||
import net.minecraft.client.gui.GuiGraphics;
|
import net.minecraft.client.gui.GuiGraphics;
|
||||||
import net.minecraft.client.gui.screens.Screen;
|
import net.minecraft.client.gui.screens.Screen;
|
||||||
import net.minecraft.client.gui.screens.inventory.MenuAccess;
|
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 final Terminal terminalData;
|
||||||
private @Nullable TerminalWidget terminal;
|
private @Nullable TerminalWidget terminal;
|
||||||
|
|
||||||
|
private final ScrollWheelHandler scrollHandler = new ScrollWheelHandler();
|
||||||
|
|
||||||
public NoTermComputerScreen(T menu, Inventory player, Component title) {
|
public NoTermComputerScreen(T menu, Inventory player, Component title) {
|
||||||
super(title);
|
super(title);
|
||||||
this.menu = menu;
|
this.menu = menu;
|
||||||
@ -67,7 +70,12 @@ public class NoTermComputerScreen<T extends AbstractComputerMenu> extends Screen
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean mouseScrolled(double mouseX, double mouseY, double scrollX, double scrollY) {
|
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);
|
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.Button;
|
||||||
import net.minecraft.client.gui.components.MultiLineLabel;
|
import net.minecraft.client.gui.components.MultiLineLabel;
|
||||||
import net.minecraft.client.gui.screens.Screen;
|
import net.minecraft.client.gui.screens.Screen;
|
||||||
|
import net.minecraft.client.renderer.RenderType;
|
||||||
import net.minecraft.network.chat.Component;
|
import net.minecraft.network.chat.Component;
|
||||||
import net.minecraft.resources.ResourceLocation;
|
import net.minecraft.resources.ResourceLocation;
|
||||||
import org.jspecify.annotations.Nullable;
|
import org.jspecify.annotations.Nullable;
|
||||||
@ -87,12 +88,13 @@ public final class OptionScreen extends Screen {
|
|||||||
@Override
|
@Override
|
||||||
public void render(GuiGraphics graphics, int mouseX, int mouseY, float partialTicks) {
|
public void render(GuiGraphics graphics, int mouseX, int mouseY, float partialTicks) {
|
||||||
// Render the actual texture.
|
// Render the actual texture.
|
||||||
graphics.blit(BACKGROUND, x, y, 0, 0, innerWidth, PADDING);
|
graphics.blit(RenderType::guiTextured, BACKGROUND, x, y, 0, 0, innerWidth, PADDING, 256, 256);
|
||||||
graphics.blit(BACKGROUND,
|
graphics.blit(RenderType::guiTextured, BACKGROUND,
|
||||||
x, y + PADDING, 0, PADDING, innerWidth, innerHeight - PADDING * 2,
|
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);
|
assertNonNull(messageRenderer).renderLeftAlignedNoShadow(graphics, x + PADDING, y + PADDING, FONT_HEIGHT, 0x404040);
|
||||||
super.render(graphics, mouseX, mouseY, partialTicks);
|
super.render(graphics, mouseX, mouseY, partialTicks);
|
||||||
|
@ -7,6 +7,7 @@ package dan200.computercraft.client.gui;
|
|||||||
import dan200.computercraft.shared.peripheral.printer.PrinterMenu;
|
import dan200.computercraft.shared.peripheral.printer.PrinterMenu;
|
||||||
import net.minecraft.client.gui.GuiGraphics;
|
import net.minecraft.client.gui.GuiGraphics;
|
||||||
import net.minecraft.client.gui.screens.inventory.AbstractContainerScreen;
|
import net.minecraft.client.gui.screens.inventory.AbstractContainerScreen;
|
||||||
|
import net.minecraft.client.renderer.RenderType;
|
||||||
import net.minecraft.network.chat.Component;
|
import net.minecraft.network.chat.Component;
|
||||||
import net.minecraft.resources.ResourceLocation;
|
import net.minecraft.resources.ResourceLocation;
|
||||||
import net.minecraft.world.entity.player.Inventory;
|
import net.minecraft.world.entity.player.Inventory;
|
||||||
@ -23,9 +24,11 @@ public class PrinterScreen extends AbstractContainerScreen<PrinterMenu> {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void renderBg(GuiGraphics graphics, float partialTicks, int mouseX, int mouseY) {
|
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
|
@Override
|
||||||
|
@ -10,6 +10,7 @@ import dan200.computercraft.shared.media.PrintoutMenu;
|
|||||||
import dan200.computercraft.shared.media.items.PrintoutData;
|
import dan200.computercraft.shared.media.items.PrintoutData;
|
||||||
import net.minecraft.client.gui.GuiGraphics;
|
import net.minecraft.client.gui.GuiGraphics;
|
||||||
import net.minecraft.client.gui.screens.inventory.AbstractContainerScreen;
|
import net.minecraft.client.gui.screens.inventory.AbstractContainerScreen;
|
||||||
|
import net.minecraft.client.renderer.LightTexture;
|
||||||
import net.minecraft.network.chat.Component;
|
import net.minecraft.network.chat.Component;
|
||||||
import net.minecraft.world.entity.player.Inventory;
|
import net.minecraft.world.entity.player.Inventory;
|
||||||
import net.minecraft.world.inventory.AbstractContainerMenu;
|
import net.minecraft.world.inventory.AbstractContainerMenu;
|
||||||
@ -20,7 +21,6 @@ import org.lwjgl.glfw.GLFW;
|
|||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
|
|
||||||
import static dan200.computercraft.client.render.PrintoutRenderer.*;
|
import static dan200.computercraft.client.render.PrintoutRenderer.*;
|
||||||
import static dan200.computercraft.client.render.RenderTypes.FULL_BRIGHT_LIGHTMAP;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The GUI for printed pages and books.
|
* The GUI for printed pages and books.
|
||||||
@ -116,8 +116,10 @@ public final class PrintoutScreen extends AbstractContainerScreen<PrintoutMenu>
|
|||||||
graphics.pose().pushPose();
|
graphics.pose().pushPose();
|
||||||
graphics.pose().translate(0, 0, 1);
|
graphics.pose().translate(0, 0, 1);
|
||||||
|
|
||||||
drawBorder(graphics.pose(), graphics.bufferSource(), leftPos, topPos, 0, page, printout.pages(), printout.book(), FULL_BRIGHT_LIGHTMAP);
|
graphics.drawSpecial(bufferSource -> {
|
||||||
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());
|
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();
|
graphics.pose().popPose();
|
||||||
}
|
}
|
||||||
|
@ -7,12 +7,12 @@ package dan200.computercraft.client.gui;
|
|||||||
import dan200.computercraft.api.ComputerCraftAPI;
|
import dan200.computercraft.api.ComputerCraftAPI;
|
||||||
import dan200.computercraft.client.gui.widgets.ComputerSidebar;
|
import dan200.computercraft.client.gui.widgets.ComputerSidebar;
|
||||||
import dan200.computercraft.client.gui.widgets.TerminalWidget;
|
import dan200.computercraft.client.gui.widgets.TerminalWidget;
|
||||||
import dan200.computercraft.client.render.RenderTypes;
|
|
||||||
import dan200.computercraft.client.render.SpriteRenderer;
|
import dan200.computercraft.client.render.SpriteRenderer;
|
||||||
import dan200.computercraft.shared.computer.core.ComputerFamily;
|
import dan200.computercraft.shared.computer.core.ComputerFamily;
|
||||||
import dan200.computercraft.shared.computer.inventory.AbstractComputerMenu;
|
import dan200.computercraft.shared.computer.inventory.AbstractComputerMenu;
|
||||||
import dan200.computercraft.shared.turtle.inventory.TurtleMenu;
|
import dan200.computercraft.shared.turtle.inventory.TurtleMenu;
|
||||||
import net.minecraft.client.gui.GuiGraphics;
|
import net.minecraft.client.gui.GuiGraphics;
|
||||||
|
import net.minecraft.client.renderer.RenderType;
|
||||||
import net.minecraft.network.chat.Component;
|
import net.minecraft.network.chat.Component;
|
||||||
import net.minecraft.resources.ResourceLocation;
|
import net.minecraft.resources.ResourceLocation;
|
||||||
import net.minecraft.world.entity.player.Inventory;
|
import net.minecraft.world.entity.player.Inventory;
|
||||||
@ -49,8 +49,11 @@ public class TurtleScreen extends AbstractComputerScreen<TurtleMenu> {
|
|||||||
@Override
|
@Override
|
||||||
protected void renderBg(GuiGraphics graphics, float partialTicks, int mouseX, int mouseY) {
|
protected void renderBg(GuiGraphics graphics, float partialTicks, int mouseX, int mouseY) {
|
||||||
var advanced = family == ComputerFamily.ADVANCED;
|
var advanced = family == ComputerFamily.ADVANCED;
|
||||||
var texture = advanced ? BACKGROUND_ADVANCED : BACKGROUND_NORMAL;
|
graphics.blit(
|
||||||
graphics.blit(texture, leftPos + AbstractComputerMenu.SIDEBAR_WIDTH, topPos, 0, 0, 0, TEX_WIDTH, TEX_HEIGHT, FULL_TEX_SIZE, FULL_TEX_SIZE);
|
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
|
// Render selected slot
|
||||||
var slot = getMenu().getSelectedSlot();
|
var slot = getMenu().getSelectedSlot();
|
||||||
@ -58,14 +61,14 @@ public class TurtleScreen extends AbstractComputerScreen<TurtleMenu> {
|
|||||||
var slotX = slot % 4;
|
var slotX = slot % 4;
|
||||||
var slotY = slot / 4;
|
var slotY = slot / 4;
|
||||||
graphics.blitSprite(
|
graphics.blitSprite(
|
||||||
advanced ? SELECTED_ADVANCED : SELECTED_NORMAL,
|
RenderType::guiTextured, advanced ? SELECTED_ADVANCED : SELECTED_NORMAL,
|
||||||
leftPos + TURTLE_START_X - 2 + slotX * 18, topPos + PLAYER_START_Y - 2 + slotY * 18, 0, 22, 22
|
leftPos + TURTLE_START_X - 2 + slotX * 18, topPos + PLAYER_START_Y - 2 + slotY * 18, 22, 22
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Render sidebar
|
// Render sidebar
|
||||||
var spriteRenderer = SpriteRenderer.createForGui(graphics, RenderTypes.GUI_SPRITES);
|
SpriteRenderer.inGui(graphics, spriteRenderer ->
|
||||||
ComputerSidebar.renderBackground(spriteRenderer, GuiSprites.getComputerTextures(family), leftPos, topPos + sidebarYOffset);
|
ComputerSidebar.renderBackground(spriteRenderer, GuiSprites.getComputerTextures(family), leftPos, topPos + sidebarYOffset)
|
||||||
graphics.flush(); // Flush to ensure background textures are drawn before foreground.
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -4,12 +4,12 @@
|
|||||||
|
|
||||||
package dan200.computercraft.client.gui.widgets;
|
package dan200.computercraft.client.gui.widgets;
|
||||||
|
|
||||||
import com.mojang.blaze3d.systems.RenderSystem;
|
|
||||||
import it.unimi.dsi.fastutil.booleans.Boolean2ObjectFunction;
|
import it.unimi.dsi.fastutil.booleans.Boolean2ObjectFunction;
|
||||||
import net.minecraft.ChatFormatting;
|
import net.minecraft.ChatFormatting;
|
||||||
import net.minecraft.client.gui.GuiGraphics;
|
import net.minecraft.client.gui.GuiGraphics;
|
||||||
import net.minecraft.client.gui.components.Button;
|
import net.minecraft.client.gui.components.Button;
|
||||||
import net.minecraft.client.gui.components.Tooltip;
|
import net.minecraft.client.gui.components.Tooltip;
|
||||||
|
import net.minecraft.client.renderer.RenderType;
|
||||||
import net.minecraft.network.chat.Component;
|
import net.minecraft.network.chat.Component;
|
||||||
import net.minecraft.resources.ResourceLocation;
|
import net.minecraft.resources.ResourceLocation;
|
||||||
import org.jspecify.annotations.Nullable;
|
import org.jspecify.annotations.Nullable;
|
||||||
@ -48,10 +48,7 @@ public class DynamicImageButton extends Button {
|
|||||||
setTooltip(message.tooltip());
|
setTooltip(message.tooltip());
|
||||||
|
|
||||||
var texture = this.texture.get(isHoveredOrFocused());
|
var texture = this.texture.get(isHoveredOrFocused());
|
||||||
|
graphics.blitSprite(RenderType::guiTextured, texture, getX(), getY(), width, height);
|
||||||
RenderSystem.disableDepthTest();
|
|
||||||
graphics.blitSprite(texture, getX(), getY(), 0, width, height);
|
|
||||||
RenderSystem.enableDepthTest();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public record HintedMessage(Component message, Tooltip tooltip) {
|
public record HintedMessage(Component message, Tooltip tooltip) {
|
||||||
|
@ -4,7 +4,6 @@
|
|||||||
|
|
||||||
package dan200.computercraft.client.gui.widgets;
|
package dan200.computercraft.client.gui.widgets;
|
||||||
|
|
||||||
import dan200.computercraft.client.render.RenderTypes;
|
|
||||||
import dan200.computercraft.client.render.text.FixedWidthFontRenderer;
|
import dan200.computercraft.client.render.text.FixedWidthFontRenderer;
|
||||||
import dan200.computercraft.core.terminal.Terminal;
|
import dan200.computercraft.core.terminal.Terminal;
|
||||||
import dan200.computercraft.core.util.StringUtil;
|
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) {
|
public void renderWidget(GuiGraphics graphics, int mouseX, int mouseY, float partialTicks) {
|
||||||
if (!visible) return;
|
if (!visible) return;
|
||||||
|
|
||||||
var emitter = FixedWidthFontRenderer.toVertexConsumer(graphics.pose(), graphics.bufferSource().getBuffer(RenderTypes.TERMINAL));
|
graphics.drawSpecial(bufferSource -> {
|
||||||
|
FixedWidthFontRenderer.drawTerminal(
|
||||||
FixedWidthFontRenderer.drawTerminal(
|
FixedWidthFontRenderer.toVertexConsumer(graphics.pose(), bufferSource.getBuffer(FixedWidthFontRenderer.TERMINAL_TEXT)),
|
||||||
emitter,
|
(float) innerX, (float) innerY, terminal, (float) MARGIN, (float) MARGIN, (float) MARGIN, (float) MARGIN
|
||||||
(float) innerX, (float) innerY, terminal, (float) MARGIN, (float) MARGIN, (float) MARGIN, (float) MARGIN
|
);
|
||||||
);
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -5,17 +5,18 @@
|
|||||||
package dan200.computercraft.client.integration;
|
package dan200.computercraft.client.integration;
|
||||||
|
|
||||||
import com.google.auto.service.AutoService;
|
import com.google.auto.service.AutoService;
|
||||||
|
import com.mojang.blaze3d.vertex.ByteBufferBuilder;
|
||||||
import com.mojang.blaze3d.vertex.VertexFormat;
|
import com.mojang.blaze3d.vertex.VertexFormat;
|
||||||
import dan200.computercraft.client.render.RenderTypes;
|
|
||||||
import dan200.computercraft.client.render.text.DirectFixedWidthFontRenderer;
|
import dan200.computercraft.client.render.text.DirectFixedWidthFontRenderer;
|
||||||
import dan200.computercraft.shared.platform.PlatformHelper;
|
import dan200.computercraft.shared.platform.PlatformHelper;
|
||||||
import net.irisshaders.iris.api.v0.IrisApi;
|
import net.irisshaders.iris.api.v0.IrisApi;
|
||||||
import net.irisshaders.iris.api.v0.IrisTextVertexSink;
|
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.nio.ByteBuffer;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
import java.util.function.IntFunction;
|
|
||||||
|
|
||||||
@AutoService(ShaderMod.Provider.class)
|
@AutoService(ShaderMod.Provider.class)
|
||||||
public class IrisShaderMod implements ShaderMod.Provider {
|
public class IrisShaderMod implements ShaderMod.Provider {
|
||||||
@ -31,7 +32,7 @@ public class IrisShaderMod implements ShaderMod.Provider {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public DirectFixedWidthFontRenderer.QuadEmitter getQuadEmitter(int vertexCount, IntFunction<ByteBuffer> makeBuffer) {
|
public DirectFixedWidthFontRenderer.QuadEmitter getQuadEmitter(int vertexCount, ByteBufferBuilder makeBuffer) {
|
||||||
return IrisApi.getInstance().getMinorApiRevision() >= 1
|
return IrisApi.getInstance().getMinorApiRevision() >= 1
|
||||||
? new IrisQuadEmitter(vertexCount, makeBuffer)
|
? new IrisQuadEmitter(vertexCount, makeBuffer)
|
||||||
: super.getQuadEmitter(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 static final class IrisQuadEmitter implements DirectFixedWidthFontRenderer.QuadEmitter {
|
||||||
private final IrisTextVertexSink sink;
|
private final IrisTextVertexSink sink;
|
||||||
|
private @Nullable ByteBuffer buffer;
|
||||||
|
|
||||||
private IrisQuadEmitter(int vertexCount, IntFunction<ByteBuffer> makeBuffer) {
|
private IrisQuadEmitter(int vertexCount, ByteBufferBuilder builder) {
|
||||||
sink = IrisApi.getInstance().createTextVertexSink(vertexCount, makeBuffer);
|
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
|
@Override
|
||||||
public VertexFormat format() {
|
public VertexFormat format() {
|
||||||
return sink.getUnderlyingVertexFormat();
|
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;
|
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.text.DirectFixedWidthFontRenderer;
|
||||||
import dan200.computercraft.client.render.vbo.DirectVertexBuffer;
|
|
||||||
|
|
||||||
import java.nio.ByteBuffer;
|
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
import java.util.ServiceLoader;
|
import java.util.ServiceLoader;
|
||||||
import java.util.function.IntFunction;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Find the currently loaded shader mod (if present) and provides utilities for interacting with it.
|
* 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 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.
|
* @return The quad emitter.
|
||||||
*/
|
*/
|
||||||
public DirectFixedWidthFontRenderer.QuadEmitter getQuadEmitter(int vertexCount, IntFunction<ByteBuffer> makeBuffer) {
|
public DirectFixedWidthFontRenderer.QuadEmitter getQuadEmitter(int vertexCount, ByteBufferBuilder buffer) {
|
||||||
return new DirectFixedWidthFontRenderer.ByteBufferEmitter(
|
return new DirectFixedWidthFontRenderer.ByteBufferEmitter(buffer);
|
||||||
makeBuffer.apply(RenderTypes.TERMINAL.format().getVertexSize() * vertexCount * 4)
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public interface Provider {
|
public interface Provider {
|
||||||
|
@ -8,7 +8,6 @@ import dan200.computercraft.api.ComputerCraftAPI;
|
|||||||
import dan200.computercraft.api.turtle.TurtleSide;
|
import dan200.computercraft.api.turtle.TurtleSide;
|
||||||
import dan200.computercraft.shared.ModRegistry;
|
import dan200.computercraft.shared.ModRegistry;
|
||||||
import dan200.computercraft.shared.integration.RecipeModHelpers;
|
import dan200.computercraft.shared.integration.RecipeModHelpers;
|
||||||
import dan200.computercraft.shared.media.items.DiskItem;
|
|
||||||
import dan200.computercraft.shared.pocket.items.PocketComputerItem;
|
import dan200.computercraft.shared.pocket.items.PocketComputerItem;
|
||||||
import dan200.computercraft.shared.turtle.items.TurtleItem;
|
import dan200.computercraft.shared.turtle.items.TurtleItem;
|
||||||
import mezz.jei.api.IModPlugin;
|
import mezz.jei.api.IModPlugin;
|
||||||
@ -23,6 +22,7 @@ import net.minecraft.client.Minecraft;
|
|||||||
import net.minecraft.core.RegistryAccess;
|
import net.minecraft.core.RegistryAccess;
|
||||||
import net.minecraft.resources.ResourceLocation;
|
import net.minecraft.resources.ResourceLocation;
|
||||||
import net.minecraft.world.item.ItemStack;
|
import net.minecraft.world.item.ItemStack;
|
||||||
|
import net.minecraft.world.item.component.DyedItemColor;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
@ -100,7 +100,7 @@ public class JEIComputerCraft implements IModPlugin {
|
|||||||
/**
|
/**
|
||||||
* Distinguishes disks by colour.
|
* 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() {
|
private static RegistryAccess getRegistryAccess() {
|
||||||
return Minecraft.getInstance().level.registryAccess();
|
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.LightTexture;
|
||||||
import net.minecraft.client.renderer.MultiBufferSource;
|
import net.minecraft.client.renderer.MultiBufferSource;
|
||||||
import net.minecraft.client.renderer.RenderType;
|
import net.minecraft.client.renderer.RenderType;
|
||||||
|
import net.minecraft.client.renderer.texture.TextureAtlas;
|
||||||
import net.minecraft.client.resources.model.Material;
|
import net.minecraft.client.resources.model.Material;
|
||||||
import net.minecraft.resources.ResourceLocation;
|
import net.minecraft.resources.ResourceLocation;
|
||||||
import net.minecraft.world.inventory.InventoryMenu;
|
|
||||||
import net.minecraft.world.item.component.DyedItemColor;
|
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_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");
|
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_NORMAL = new Material(TextureAtlas.LOCATION_BLOCKS, TEXTURE_NORMAL);
|
||||||
private static final Material MATERIAL_ADVANCED = new Material(InventoryMenu.BLOCK_ATLAS, TEXTURE_ADVANCED);
|
private static final Material MATERIAL_ADVANCED = new Material(TextureAtlas.LOCATION_BLOCKS, TEXTURE_ADVANCED);
|
||||||
private static final Material MATERIAL_COLOUR = new Material(InventoryMenu.BLOCK_ATLAS, TEXTURE_COLOUR);
|
private static final Material MATERIAL_COLOUR = new Material(TextureAtlas.LOCATION_BLOCKS, TEXTURE_COLOUR);
|
||||||
private static final Material MATERIAL_FRAME = new Material(InventoryMenu.BLOCK_ATLAS, TEXTURE_FRAME);
|
private static final Material MATERIAL_FRAME = new Material(TextureAtlas.LOCATION_BLOCKS, TEXTURE_FRAME);
|
||||||
private static final Material MATERIAL_LIGHT = new Material(InventoryMenu.BLOCK_ATLAS, TEXTURE_LIGHT);
|
private static final Material MATERIAL_LIGHT = new Material(TextureAtlas.LOCATION_BLOCKS, TEXTURE_LIGHT);
|
||||||
|
|
||||||
// The size of the terminal within the model.
|
// The size of the terminal within the model.
|
||||||
public static final float TERM_WIDTH = 12.0f / 32.0f;
|
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.PartPose;
|
||||||
import net.minecraft.client.model.geom.builders.CubeListBuilder;
|
import net.minecraft.client.model.geom.builders.CubeListBuilder;
|
||||||
import net.minecraft.client.model.geom.builders.MeshDefinition;
|
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.client.resources.model.Material;
|
||||||
import net.minecraft.resources.ResourceLocation;
|
import net.minecraft.resources.ResourceLocation;
|
||||||
import net.minecraft.world.inventory.InventoryMenu;
|
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
@ -29,7 +29,7 @@ import java.util.List;
|
|||||||
*/
|
*/
|
||||||
public class LecternPrintoutModel {
|
public class LecternPrintoutModel {
|
||||||
public static final ResourceLocation TEXTURE = ResourceLocation.fromNamespaceAndPath(ComputerCraftAPI.MOD_ID, "entity/printout");
|
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_WIDTH = 32;
|
||||||
private static final int TEXTURE_HEIGHT = 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.client.Minecraft;
|
||||||
import net.minecraft.core.BlockPos;
|
import net.minecraft.core.BlockPos;
|
||||||
import net.minecraft.core.Holder;
|
import net.minecraft.core.Holder;
|
||||||
|
import net.minecraft.core.registries.Registries;
|
||||||
import net.minecraft.network.chat.Component;
|
import net.minecraft.network.chat.Component;
|
||||||
import net.minecraft.resources.ResourceLocation;
|
import net.minecraft.resources.ResourceLocation;
|
||||||
import net.minecraft.world.entity.player.Player;
|
import net.minecraft.world.entity.player.Player;
|
||||||
import net.minecraft.world.item.JukeboxSong;
|
import net.minecraft.world.item.JukeboxSong;
|
||||||
import net.minecraft.world.level.Level;
|
import net.minecraft.world.level.Level;
|
||||||
|
import net.minecraft.world.level.block.LevelEvent;
|
||||||
import org.jspecify.annotations.Nullable;
|
import org.jspecify.annotations.Nullable;
|
||||||
|
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
@ -62,10 +64,14 @@ public final class ClientNetworkContextImpl implements ClientNetworkContext {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void handlePlayRecord(BlockPos pos, @Nullable Holder<JukeboxSong> song) {
|
public void handlePlayRecord(BlockPos pos, @Nullable Holder<JukeboxSong> song) {
|
||||||
|
var level = Minecraft.getInstance().level;
|
||||||
|
if (level == null) return;
|
||||||
|
|
||||||
if (song == null) {
|
if (song == null) {
|
||||||
Minecraft.getInstance().levelRenderer.stopJukeboxSongAndNotifyNearby(pos);
|
level.levelEvent(LevelEvent.SOUND_STOP_JUKEBOX_SONG, pos, 0);
|
||||||
} else {
|
} 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 dan200.computercraft.shared.util.WorldUtil;
|
||||||
import net.minecraft.client.Camera;
|
import net.minecraft.client.Camera;
|
||||||
import net.minecraft.client.renderer.MultiBufferSource;
|
import net.minecraft.client.renderer.MultiBufferSource;
|
||||||
import net.minecraft.client.renderer.RenderType;
|
import net.minecraft.client.renderer.ShapeRenderer;
|
||||||
import net.minecraft.util.Mth;
|
|
||||||
import net.minecraft.world.phys.BlockHitResult;
|
import net.minecraft.world.phys.BlockHitResult;
|
||||||
|
|
||||||
public final class CableHighlightRenderer {
|
public final class CableHighlightRenderer {
|
||||||
@ -27,7 +26,6 @@ public final class CableHighlightRenderer {
|
|||||||
* @param camera The current camera.
|
* @param camera The current camera.
|
||||||
* @param hit The block hit result for the current player.
|
* @param hit The block hit result for the current player.
|
||||||
* @return If we rendered a custom outline.
|
* @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) {
|
public static boolean drawHighlight(PoseStack transform, MultiBufferSource bufferSource, Camera camera, BlockHitResult hit) {
|
||||||
var pos = hit.getBlockPos();
|
var pos = hit.getBlockPos();
|
||||||
@ -49,27 +47,9 @@ public final class CableHighlightRenderer {
|
|||||||
var yOffset = pos.getY() - cameraPos.y();
|
var yOffset = pos.getY() - cameraPos.y();
|
||||||
var zOffset = pos.getZ() - cameraPos.z();
|
var zOffset = pos.getZ() - cameraPos.z();
|
||||||
|
|
||||||
var buffer = bufferSource.getBuffer(RenderType.lines());
|
BlockOutlineRenderer.render(
|
||||||
var matrix4f = transform.last().pose();
|
bufferSource, (buffer, colour) -> ShapeRenderer.renderShape(transform, buffer, shape, xOffset, yOffset, zOffset, colour)
|
||||||
// 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);
|
|
||||||
});
|
|
||||||
|
|
||||||
return true;
|
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.BlockEntityRenderer;
|
||||||
import net.minecraft.client.renderer.blockentity.BlockEntityRendererProvider;
|
import net.minecraft.client.renderer.blockentity.BlockEntityRendererProvider;
|
||||||
import net.minecraft.client.renderer.blockentity.LecternRenderer;
|
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.item.component.DyedItemColor;
|
||||||
import net.minecraft.world.level.block.LecternBlock;
|
import net.minecraft.world.level.block.LecternBlock;
|
||||||
import net.minecraft.world.phys.Vec3;
|
import net.minecraft.world.phys.Vec3;
|
||||||
@ -71,7 +71,7 @@ public class CustomLecternRenderer implements BlockEntityRenderer<CustomLecternB
|
|||||||
|
|
||||||
pocketModel.render(
|
pocketModel.render(
|
||||||
poseStack, buffer, packedLight, packedOverlay, pocket.getFamily(), DyedItemColor.getOrDefault(item, -1),
|
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.
|
// 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.
|
// Either render the terminal or a black screen, depending on how close we are.
|
||||||
var terminal = computer == null ? null : computer.getTerminal();
|
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)) {
|
if (terminal != null && Vec3.atCenterOf(lectern.getBlockPos()).closerThan(berDispatcher.camera.getPosition(), POCKET_TERMINAL_RENDER_DISTANCE)) {
|
||||||
renderPocketTerminal(poseStack, quadEmitter, terminal);
|
renderPocketTerminal(poseStack, quadEmitter, terminal);
|
||||||
} else {
|
} 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.PoseStack;
|
||||||
import com.mojang.blaze3d.vertex.VertexConsumer;
|
import com.mojang.blaze3d.vertex.VertexConsumer;
|
||||||
import dan200.computercraft.client.model.turtle.ModelTransformer;
|
|
||||||
import dan200.computercraft.client.platform.ClientPlatformHelper;
|
import dan200.computercraft.client.platform.ClientPlatformHelper;
|
||||||
import net.minecraft.client.renderer.MultiBufferSource;
|
import net.minecraft.client.renderer.MultiBufferSource;
|
||||||
import net.minecraft.client.renderer.block.model.BakedQuad;
|
import net.minecraft.client.renderer.block.model.BakedQuad;
|
||||||
import net.minecraft.client.renderer.entity.ItemRenderer;
|
import net.minecraft.client.renderer.entity.ItemRenderer;
|
||||||
import net.minecraft.client.resources.model.BakedModel;
|
import net.minecraft.client.resources.model.BakedModel;
|
||||||
import net.minecraft.world.item.ItemStack;
|
import net.minecraft.util.ARGB;
|
||||||
import org.joml.Vector4f;
|
|
||||||
import org.jspecify.annotations.Nullable;
|
import org.jspecify.annotations.Nullable;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
@ -29,8 +27,7 @@ public final class ModelRenderer {
|
|||||||
* Render a list of {@linkplain BakedQuad quads} to a buffer.
|
* Render a list of {@linkplain BakedQuad quads} to a buffer.
|
||||||
* <p>
|
* <p>
|
||||||
* This is not intended to be used directly, but instead by {@link ClientPlatformHelper#renderBakedModel(PoseStack, MultiBufferSource, BakedModel, int, int, int[])}. The
|
* 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)},
|
* implementation here is identical to {@link ItemRenderer#renderQuadList(PoseStack, VertexConsumer, List, int[], int, int)}.
|
||||||
* but supports inverted quads (i.e. those with a negative scale).
|
|
||||||
*
|
*
|
||||||
* @param transform The current matrix transformation to apply.
|
* @param transform The current matrix transformation to apply.
|
||||||
* @param buffer The buffer to draw to.
|
* @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) {
|
public static void renderQuads(PoseStack transform, VertexConsumer buffer, List<BakedQuad> quads, int lightmapCoord, int overlayLight, int @Nullable [] tints) {
|
||||||
var matrix = transform.last();
|
var matrix = transform.last();
|
||||||
var inverted = matrix.pose().determinant() < 0;
|
|
||||||
|
|
||||||
for (var bakedquad : quads) {
|
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()) {
|
if (tints != null && bakedquad.isTinted()) {
|
||||||
var idx = bakedquad.getTintIndex();
|
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);
|
buffer.putBulkData(matrix, bakedquad, r, g, b, a, lightmapCoord, overlayLight);
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 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
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -13,8 +13,10 @@ import dan200.computercraft.core.util.Colour;
|
|||||||
import dan200.computercraft.shared.computer.core.ComputerFamily;
|
import dan200.computercraft.shared.computer.core.ComputerFamily;
|
||||||
import dan200.computercraft.shared.config.Config;
|
import dan200.computercraft.shared.config.Config;
|
||||||
import dan200.computercraft.shared.pocket.items.PocketComputerItem;
|
import dan200.computercraft.shared.pocket.items.PocketComputerItem;
|
||||||
|
import net.minecraft.client.renderer.LightTexture;
|
||||||
import net.minecraft.client.renderer.MultiBufferSource;
|
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.ItemStack;
|
||||||
import net.minecraft.world.item.component.DyedItemColor;
|
import net.minecraft.world.item.component.DyedItemColor;
|
||||||
import org.joml.Matrix4f;
|
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();
|
var lightColour = computer == null || computer.getLightState() == -1 ? Colour.BLACK.getHex() : computer.getLightState();
|
||||||
renderLight(transform, bufferSource, lightColour, width, height);
|
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) {
|
if (terminal == null) {
|
||||||
FixedWidthFontRenderer.drawEmptyTerminal(quadEmitter, 0, 0, width, height);
|
FixedWidthFontRenderer.drawEmptyTerminal(quadEmitter, 0, 0, width, height);
|
||||||
} else {
|
} else {
|
||||||
@ -89,16 +91,16 @@ public final class PocketItemRenderer extends ItemMapLikeRenderer {
|
|||||||
var g = (colour >>> 8) & 0xFF;
|
var g = (colour >>> 8) & 0xFF;
|
||||||
var b = colour & 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);
|
ComputerBorderRenderer.render(spriteRenderer, texture, 0, 0, width, height, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void renderLight(PoseStack transform, MultiBufferSource render, int colour, int width, int height) {
|
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.drawQuad(
|
||||||
FixedWidthFontRenderer.toVertexConsumer(transform, buffer),
|
FixedWidthFontRenderer.toVertexConsumer(transform, buffer),
|
||||||
width - LIGHT_HEIGHT * 2, height + BORDER / 2.0f, 0.001f, LIGHT_HEIGHT * 2, LIGHT_HEIGHT,
|
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 com.mojang.math.Axis;
|
||||||
import dan200.computercraft.shared.ModRegistry;
|
import dan200.computercraft.shared.ModRegistry;
|
||||||
import dan200.computercraft.shared.media.items.PrintoutData;
|
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.MultiBufferSource;
|
||||||
import net.minecraft.world.entity.EntityType;
|
import net.minecraft.client.renderer.entity.state.ItemFrameRenderState;
|
||||||
import net.minecraft.world.entity.decoration.ItemFrame;
|
|
||||||
import net.minecraft.world.item.ItemStack;
|
import net.minecraft.world.item.ItemStack;
|
||||||
|
|
||||||
import static dan200.computercraft.client.render.PrintoutRenderer.*;
|
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.scale(0.42f, 0.42f, -0.42f);
|
||||||
transform.translate(-0.5f, -0.48f, 0.0f);
|
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
|
// Move a little bit forward to ensure we're not clipping with the frame
|
||||||
transform.translate(0.0f, 0.0f, -0.001f);
|
transform.translate(0.0f, 0.0f, -0.001f);
|
||||||
transform.mulPose(Axis.ZP.rotationDegrees(180f));
|
transform.mulPose(Axis.ZP.rotationDegrees(180f));
|
||||||
transform.scale(0.95f, 0.95f, -0.95f);
|
transform.scale(0.95f, 0.95f, -0.95f);
|
||||||
transform.translate(-0.5f, -0.5f, 0.0f);
|
transform.translate(-0.5f, -0.5f, 0.0f);
|
||||||
|
|
||||||
var light = frame.getType() == EntityType.GLOW_ITEM_FRAME ? 0xf000d2 : packedLight; // See getLightVal.
|
var light = frame.isGlowFrame ? 0xf000d2 : packedLight; // See getLightCoords.
|
||||||
drawPrintout(transform, render, stack, light);
|
drawPrintout(transform, render, data, isBook, light);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void drawPrintout(PoseStack transform, MultiBufferSource render, ItemStack stack, int light) {
|
private static void drawPrintout(PoseStack transform, MultiBufferSource render, PrintoutData pageData, boolean book, int light) {
|
||||||
var pageData = stack.getOrDefault(ModRegistry.DataComponents.PRINTOUT.get(), PrintoutData.EMPTY);
|
|
||||||
|
|
||||||
var pages = pageData.pages();
|
var pages = pageData.pages();
|
||||||
var book = ((PrintoutItem) stack.getItem()).getType() == PrintoutItem.Type.BOOK;
|
|
||||||
|
|
||||||
double width = LINE_LENGTH * FONT_WIDTH + X_TEXT_MARGIN * 2;
|
double width = LINE_LENGTH * FONT_WIDTH + X_TEXT_MARGIN * 2;
|
||||||
double height = LINES_PER_PAGE * FONT_HEIGHT + Y_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.core.terminal.TextBuffer;
|
||||||
import dan200.computercraft.shared.media.items.PrintoutData;
|
import dan200.computercraft.shared.media.items.PrintoutData;
|
||||||
import net.minecraft.client.renderer.MultiBufferSource;
|
import net.minecraft.client.renderer.MultiBufferSource;
|
||||||
|
import net.minecraft.client.renderer.RenderType;
|
||||||
|
import net.minecraft.resources.ResourceLocation;
|
||||||
import org.joml.Matrix4f;
|
import org.joml.Matrix4f;
|
||||||
|
|
||||||
import java.util.List;
|
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}.
|
* {@linkplain PrintoutItemRenderer in-hand/item frame printouts}.
|
||||||
*/
|
*/
|
||||||
public final class PrintoutRenderer {
|
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;
|
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_Y = Y_SIZE;
|
||||||
private static final int COVER_X = X_SIZE + 4 * X_FOLD_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() {
|
private PrintoutRenderer() {
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void drawText(PoseStack transform, MultiBufferSource bufferSource, int x, int y, int start, int light, TextBuffer[] text, TextBuffer[] colours) {
|
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);
|
var emitter = FixedWidthFontRenderer.toVertexConsumer(transform, buffer);
|
||||||
for (var line = 0; line < LINES_PER_PAGE && line < text.length; line++) {
|
for (var line = 0; line < LINES_PER_PAGE && line < text.length; line++) {
|
||||||
FixedWidthFontRenderer.drawString(emitter,
|
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) {
|
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);
|
var emitter = FixedWidthFontRenderer.toVertexConsumer(transform, buffer);
|
||||||
for (var line = 0; line < LINES_PER_PAGE && line < lines.size(); line++) {
|
for (var line = 0; line < LINES_PER_PAGE && line < lines.size(); line++) {
|
||||||
var lineContents = lines.get(start + line);
|
var lineContents = lines.get(start + line);
|
||||||
@ -90,7 +101,7 @@ public final class PrintoutRenderer {
|
|||||||
var leftPages = page;
|
var leftPages = page;
|
||||||
var rightPages = pages - page - 1;
|
var rightPages = pages - page - 1;
|
||||||
|
|
||||||
var buffer = bufferSource.getBuffer(RenderTypes.PRINTOUT_BACKGROUND);
|
var buffer = bufferSource.getBuffer(BACKGROUND);
|
||||||
|
|
||||||
if (isBook) {
|
if (isBook) {
|
||||||
// Border
|
// Border
|
||||||
@ -99,12 +110,12 @@ public final class PrintoutRenderer {
|
|||||||
var right = x + X_SIZE + offset - 4;
|
var right = x + X_SIZE + offset - 4;
|
||||||
|
|
||||||
// Left and right border
|
// 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, 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 - 0.02f, COVER_X + COVER_SIZE, 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).
|
// Draw centre panel (just stretched texture, sorry).
|
||||||
drawTexture(matrix, buffer,
|
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,
|
COVER_X + COVER_SIZE / 2.0f, COVER_SIZE, COVER_SIZE, Y_SIZE,
|
||||||
light
|
light
|
||||||
);
|
);
|
||||||
@ -112,20 +123,20 @@ public final class PrintoutRenderer {
|
|||||||
var borderX = left;
|
var borderX = left;
|
||||||
while (borderX < right) {
|
while (borderX < right) {
|
||||||
double thisWidth = Math.min(right - borderX, X_SIZE);
|
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 - 8, z + BOOK_Z_OFFSET, 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 + Y_SIZE - 4, z + BOOK_Z_OFFSET, 0, COVER_Y + COVER_SIZE, (float) thisWidth, COVER_SIZE, light);
|
||||||
borderX = (float) (borderX + thisWidth);
|
borderX = (float) (borderX + thisWidth);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Current page background: Z-offset is interleaved between the "zeroth" left/right page and the first
|
// 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.
|
// 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
|
// Left pages
|
||||||
for (var n = 0; n <= leftPages; n++) {
|
for (var n = 0; n <= leftPages; n++) {
|
||||||
drawTexture(matrix, buffer,
|
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
|
// Use the left "bold" fold for the outermost page
|
||||||
n == leftPages ? 0 : X_FOLD_SIZE, 0,
|
n == leftPages ? 0 : X_FOLD_SIZE, 0,
|
||||||
X_FOLD_SIZE, Y_SIZE, light
|
X_FOLD_SIZE, Y_SIZE, light
|
||||||
@ -135,7 +146,7 @@ public final class PrintoutRenderer {
|
|||||||
// Right pages
|
// Right pages
|
||||||
for (var n = 0; n <= rightPages; n++) {
|
for (var n = 0; n <= rightPages; n++) {
|
||||||
drawTexture(matrix, buffer,
|
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.
|
// 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 * 2 + X_SIZE + (n == rightPages ? X_FOLD_SIZE : 0), 0,
|
||||||
X_FOLD_SIZE, Y_SIZE, light
|
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;
|
package dan200.computercraft.client.render;
|
||||||
|
|
||||||
import com.mojang.blaze3d.vertex.VertexConsumer;
|
import com.mojang.blaze3d.vertex.VertexConsumer;
|
||||||
|
import dan200.computercraft.client.gui.GuiSprites;
|
||||||
import net.minecraft.client.gui.GuiGraphics;
|
import net.minecraft.client.gui.GuiGraphics;
|
||||||
|
import net.minecraft.client.renderer.LightTexture;
|
||||||
import net.minecraft.client.renderer.RenderType;
|
import net.minecraft.client.renderer.RenderType;
|
||||||
import net.minecraft.client.renderer.texture.TextureAtlasSprite;
|
import net.minecraft.client.renderer.texture.TextureAtlasSprite;
|
||||||
import org.joml.Matrix4f;
|
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
|
* A {@link GuiGraphics}-equivalent which is suitable for both rendering in to a GUI and in-world (as part of an entity
|
||||||
* renderer).
|
* renderer).
|
||||||
@ -34,11 +38,11 @@ public class SpriteRenderer {
|
|||||||
this.b = b;
|
this.b = b;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static SpriteRenderer createForGui(GuiGraphics graphics, RenderType renderType) {
|
public static void inGui(GuiGraphics graphics, Consumer<SpriteRenderer> renderer) {
|
||||||
return new SpriteRenderer(
|
graphics.drawSpecial(bufferSource -> renderer.accept(new SpriteRenderer(
|
||||||
graphics.pose().last().pose(), graphics.bufferSource().getBuffer(renderType),
|
graphics.pose().last().pose(), bufferSource.getBuffer(RenderType.guiTextured(GuiSprites.TEXTURE)),
|
||||||
0, RenderTypes.FULL_BRIGHT_LIGHTMAP, 255, 255, 255
|
0, LightTexture.FULL_BRIGHT, 255, 255, 255
|
||||||
);
|
)));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -68,7 +72,7 @@ public class SpriteRenderer {
|
|||||||
* @param textureWidth The width of the whole texture.
|
* @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) {
|
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");
|
if (width < leftBorder + rightBorder) throw new IllegalArgumentException("width is less than two borders");
|
||||||
|
|
||||||
var centerStart = SpriteRenderer.u(sprite, leftBorder, textureWidth);
|
var centerStart = SpriteRenderer.u(sprite, leftBorder, textureWidth);
|
||||||
@ -93,7 +97,7 @@ public class SpriteRenderer {
|
|||||||
* @param textureHeight The height of the whole texture.
|
* @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) {
|
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");
|
if (width < topBorder + bottomBorder) throw new IllegalArgumentException("height is less than two borders");
|
||||||
|
|
||||||
var centerStart = SpriteRenderer.v(sprite, topBorder, textureHeight);
|
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.blaze3d.vertex.PoseStack;
|
||||||
import com.mojang.math.Axis;
|
import com.mojang.math.Axis;
|
||||||
import com.mojang.math.Transformation;
|
|
||||||
import dan200.computercraft.api.ComputerCraftAPI;
|
import dan200.computercraft.api.ComputerCraftAPI;
|
||||||
|
import dan200.computercraft.api.client.TransformedModel;
|
||||||
import dan200.computercraft.api.turtle.TurtleSide;
|
import dan200.computercraft.api.turtle.TurtleSide;
|
||||||
import dan200.computercraft.client.platform.ClientPlatformHelper;
|
import dan200.computercraft.client.platform.ClientPlatformHelper;
|
||||||
import dan200.computercraft.client.turtle.TurtleUpgradeModellers;
|
import dan200.computercraft.client.turtle.TurtleUpgradeModellers;
|
||||||
|
import dan200.computercraft.shared.computer.core.ComputerFamily;
|
||||||
import dan200.computercraft.shared.turtle.TurtleOverlay;
|
import dan200.computercraft.shared.turtle.TurtleOverlay;
|
||||||
import dan200.computercraft.shared.turtle.blocks.TurtleBlockEntity;
|
import dan200.computercraft.shared.turtle.blocks.TurtleBlockEntity;
|
||||||
import dan200.computercraft.shared.util.Holiday;
|
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.renderer.blockentity.BlockEntityRendererProvider;
|
||||||
import net.minecraft.client.resources.model.BakedModel;
|
import net.minecraft.client.resources.model.BakedModel;
|
||||||
import net.minecraft.resources.ResourceLocation;
|
import net.minecraft.resources.ResourceLocation;
|
||||||
|
import net.minecraft.util.ARGB;
|
||||||
import net.minecraft.util.CommonColors;
|
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.BlockHitResult;
|
||||||
import net.minecraft.world.phys.HitResult;
|
import net.minecraft.world.phys.HitResult;
|
||||||
import org.jspecify.annotations.Nullable;
|
import org.jspecify.annotations.Nullable;
|
||||||
|
|
||||||
public class TurtleBlockEntityRenderer implements BlockEntityRenderer<TurtleBlockEntity> {
|
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");
|
public static final ResourceLocation COLOUR_TURTLE_MODEL = ResourceLocation.fromNamespaceAndPath(ComputerCraftAPI.MOD_ID, "block/turtle_colour");
|
||||||
|
|
||||||
private final BlockEntityRenderDispatcher renderer;
|
private final BlockEntityRenderDispatcher renderer;
|
||||||
@ -72,9 +77,6 @@ public class TurtleBlockEntityRenderer implements BlockEntityRenderer<TurtleBloc
|
|||||||
transform.translate(0.5f, 0.5f, 0.5f);
|
transform.translate(0.5f, 0.5f, 0.5f);
|
||||||
var yaw = turtle.getRenderYaw(partialTicks);
|
var yaw = turtle.getRenderYaw(partialTicks);
|
||||||
transform.mulPose(Axis.YP.rotationDegrees(180.0f - yaw));
|
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);
|
transform.translate(-0.5f, -0.5f, -0.5f);
|
||||||
|
|
||||||
// Render the turtle
|
// Render the turtle
|
||||||
@ -82,14 +84,10 @@ public class TurtleBlockEntityRenderer implements BlockEntityRenderer<TurtleBloc
|
|||||||
var overlay = turtle.getOverlay();
|
var overlay = turtle.getOverlay();
|
||||||
|
|
||||||
if (colour == -1) {
|
if (colour == -1) {
|
||||||
// Render the turtle using its item model.
|
renderModel(transform, buffers, lightmapCoord, overlayLight, turtle.getFamily() == ComputerFamily.NORMAL ? NORMAL_TURTLE_MODEL : ADVANCED_TURTLE_MODEL, null);
|
||||||
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);
|
|
||||||
} else {
|
} else {
|
||||||
// Otherwise render it using the colour item.
|
// 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
|
// Render the overlay
|
||||||
@ -116,15 +114,25 @@ public class TurtleBlockEntityRenderer implements BlockEntityRenderer<TurtleBloc
|
|||||||
transform.mulPose(Axis.XN.rotationDegrees(toolAngle));
|
transform.mulPose(Axis.XN.rotationDegrees(toolAngle));
|
||||||
transform.translate(0.0f, -0.5f, -0.5f);
|
transform.translate(0.0f, -0.5f, -0.5f);
|
||||||
|
|
||||||
var model = TurtleUpgradeModellers.getModel(upgrade, turtle.getAccess(), side);
|
switch (TurtleUpgradeModellers.getModel(upgrade, turtle.getAccess(), side)) {
|
||||||
applyTransformation(transform, model.matrix());
|
case TransformedModel.Item model -> {
|
||||||
renderModel(transform, buffers, lightmapCoord, overlayLight, model.model(), null);
|
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();
|
transform.popPose();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void renderModel(PoseStack transform, MultiBufferSource buffers, int lightmapCoord, int overlayLight, ResourceLocation modelLocation, int @Nullable [] tints) {
|
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);
|
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) {
|
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);
|
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;
|
package dan200.computercraft.client.render.monitor;
|
||||||
|
|
||||||
import com.mojang.blaze3d.platform.GlStateManager;
|
|
||||||
import com.mojang.blaze3d.systems.RenderSystem;
|
import com.mojang.blaze3d.systems.RenderSystem;
|
||||||
import com.mojang.blaze3d.vertex.PoseStack;
|
import com.mojang.blaze3d.vertex.*;
|
||||||
import com.mojang.blaze3d.vertex.Tesselator;
|
|
||||||
import com.mojang.blaze3d.vertex.VertexBuffer;
|
|
||||||
import com.mojang.blaze3d.vertex.VertexConsumer;
|
|
||||||
import com.mojang.math.Axis;
|
import com.mojang.math.Axis;
|
||||||
import dan200.computercraft.annotations.ForgeOverride;
|
import dan200.computercraft.annotations.ForgeOverride;
|
||||||
import dan200.computercraft.client.FrameInfo;
|
import dan200.computercraft.client.FrameInfo;
|
||||||
import dan200.computercraft.client.integration.ShaderMod;
|
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.DirectFixedWidthFontRenderer;
|
||||||
import dan200.computercraft.client.render.text.FixedWidthFontRenderer;
|
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.terminal.Terminal;
|
||||||
import dan200.computercraft.core.util.Nullability;
|
|
||||||
import dan200.computercraft.shared.config.Config;
|
import dan200.computercraft.shared.config.Config;
|
||||||
import dan200.computercraft.shared.peripheral.monitor.ClientMonitor;
|
import dan200.computercraft.shared.peripheral.monitor.ClientMonitor;
|
||||||
import dan200.computercraft.shared.peripheral.monitor.MonitorBlockEntity;
|
import dan200.computercraft.shared.peripheral.monitor.MonitorBlockEntity;
|
||||||
import dan200.computercraft.shared.peripheral.monitor.MonitorRenderer;
|
|
||||||
import dan200.computercraft.shared.util.DirectionUtil;
|
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.MultiBufferSource;
|
||||||
import net.minecraft.client.renderer.blockentity.BlockEntityRenderer;
|
import net.minecraft.client.renderer.blockentity.BlockEntityRenderer;
|
||||||
import net.minecraft.client.renderer.blockentity.BlockEntityRendererProvider;
|
import net.minecraft.client.renderer.blockentity.BlockEntityRendererProvider;
|
||||||
import net.minecraft.world.phys.AABB;
|
import net.minecraft.world.phys.AABB;
|
||||||
import org.joml.Matrix4f;
|
import org.joml.Matrix4f;
|
||||||
import org.jspecify.annotations.Nullable;
|
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 java.util.function.Consumer;
|
||||||
|
|
||||||
import static dan200.computercraft.client.render.text.FixedWidthFontRenderer.FONT_HEIGHT;
|
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 final float MARGIN = (float) (MonitorBlockEntity.RENDER_MARGIN * 1.1);
|
||||||
|
|
||||||
private static @Nullable ByteBuffer backingBuffer;
|
private static final ByteBufferBuilder backingBufferBuilder = new ByteBufferBuilder(0x4000);
|
||||||
|
|
||||||
private static long lastFrame = -1;
|
|
||||||
|
|
||||||
public MonitorBlockEntityRenderer(BlockEntityRendererProvider.Context context) {
|
public MonitorBlockEntityRenderer(BlockEntityRendererProvider.Context context) {
|
||||||
}
|
}
|
||||||
@ -76,7 +62,6 @@ public class MonitorBlockEntityRenderer implements BlockEntityRenderer<MonitorBl
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
lastFrame = renderFrame;
|
|
||||||
renderState.lastRenderFrame = renderFrame;
|
renderState.lastRenderFrame = renderFrame;
|
||||||
renderState.lastRenderPos = monitorPos;
|
renderState.lastRenderPos = monitorPos;
|
||||||
|
|
||||||
@ -124,7 +109,7 @@ public class MonitorBlockEntityRenderer implements BlockEntityRenderer<MonitorBl
|
|||||||
transform.popPose();
|
transform.popPose();
|
||||||
} else {
|
} else {
|
||||||
FixedWidthFontRenderer.drawEmptyTerminal(
|
FixedWidthFontRenderer.drawEmptyTerminal(
|
||||||
FixedWidthFontRenderer.toVertexConsumer(transform, bufferSource.getBuffer(RenderTypes.TERMINAL)),
|
FixedWidthFontRenderer.toVertexConsumer(transform, bufferSource.getBuffer(FixedWidthFontRenderer.TERMINAL_TEXT)),
|
||||||
-MARGIN, MARGIN,
|
-MARGIN, MARGIN,
|
||||||
(float) (xSize + 2 * MARGIN), (float) -(ySize + MARGIN * 2)
|
(float) (xSize + 2 * MARGIN), (float) -(ySize + MARGIN * 2)
|
||||||
);
|
);
|
||||||
@ -136,126 +121,100 @@ public class MonitorBlockEntityRenderer implements BlockEntityRenderer<MonitorBl
|
|||||||
private static void renderTerminal(
|
private static void renderTerminal(
|
||||||
Matrix4f matrix, ClientMonitor monitor, MonitorRenderState renderState, Terminal terminal, float xMargin, float yMargin
|
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();
|
var redraw = monitor.pollTerminalChanged();
|
||||||
if (renderState.createBuffer(renderType)) redraw = true;
|
if (renderState.createBuffer()) redraw = true;
|
||||||
|
|
||||||
switch (renderType) {
|
var backgroundBuffer = assertNonNull(renderState.backgroundBuffer);
|
||||||
case TBO -> {
|
var foregroundBuffer = assertNonNull(renderState.foregroundBuffer);
|
||||||
if (redraw) {
|
if (redraw) {
|
||||||
var terminalBuffer = getBuffer(width * height * 3);
|
var size = DirectFixedWidthFontRenderer.getVertexCount(terminal);
|
||||||
MonitorTextureBufferShader.setTerminalData(terminalBuffer, terminal);
|
|
||||||
DirectBuffers.setBufferData(GL31.GL_TEXTURE_BUFFER, renderState.tboBuffer, terminalBuffer, GL20.GL_STATIC_DRAW);
|
|
||||||
|
|
||||||
var uniformBuffer = getBuffer(MonitorTextureBufferShader.UNIFORM_SIZE);
|
// In an ideal world we could upload these both into one buffer. However, we can't render VBOs with
|
||||||
MonitorTextureBufferShader.setUniformData(uniformBuffer, terminal);
|
// a starting and ending offset, and so need to use two buffers instead.
|
||||||
DirectBuffers.setBufferData(GL31.GL_UNIFORM_BUFFER, renderState.tboUniform, uniformBuffer, GL20.GL_STATIC_DRAW);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Nobody knows what they're doing!
|
renderToBuffer(backgroundBuffer, size, sink ->
|
||||||
var active = GlStateManager._getActiveTexture();
|
DirectFixedWidthFontRenderer.drawTerminalBackground(sink, 0, 0, terminal, yMargin, yMargin, xMargin, xMargin));
|
||||||
RenderSystem.activeTexture(MonitorTextureBufferShader.TEXTURE_INDEX);
|
|
||||||
GL11.glBindTexture(GL31.GL_TEXTURE_BUFFER, renderState.tboTexture);
|
|
||||||
RenderSystem.activeTexture(active);
|
|
||||||
|
|
||||||
var shader = RenderTypes.getMonitorTextureBufferShader();
|
renderToBuffer(foregroundBuffer, size + 4, sink -> {
|
||||||
shader.setupUniform(renderState.tboUniform);
|
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
|
||||||
var buffer = Tesselator.getInstance().begin(RenderTypes.MONITOR_TBO.mode(), RenderTypes.MONITOR_TBO.format());
|
// render n or n+1 quads and so toggle the cursor on and off.
|
||||||
tboVertex(buffer, matrix, -xMargin, -yMargin);
|
DirectFixedWidthFontRenderer.drawCursor(sink, 0, 0, terminal);
|
||||||
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");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 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) {
|
private static void renderToBuffer(VertexBuffer vbo, int size, Consumer<DirectFixedWidthFontRenderer.QuadEmitter> draw) {
|
||||||
var sink = ShaderMod.get().getQuadEmitter(size, MonitorBlockEntityRenderer::getBuffer);
|
var sink = ShaderMod.get().getQuadEmitter(size, backingBufferBuilder);
|
||||||
var buffer = sink.buffer();
|
|
||||||
|
|
||||||
draw.accept(sink);
|
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) {
|
var result = backingBufferBuilder.build();
|
||||||
// We encode position in the UV, as that's not transformed by the matrix.
|
if (result == null) {
|
||||||
builder.addVertex(matrix, x, y, 0).setUv(x, y);
|
// If we have nothing to draw, just mark it as empty. We'll skip drawing in drawWithShader.
|
||||||
}
|
vbo.indexCount = 0;
|
||||||
|
return;
|
||||||
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);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
buffer.clear();
|
var buffer = result.byteBuffer();
|
||||||
return buffer;
|
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
|
@Override
|
||||||
@ -267,28 +226,4 @@ public class MonitorBlockEntityRenderer implements BlockEntityRenderer<MonitorBl
|
|||||||
public AABB getRenderBoundingBox(MonitorBlockEntity monitor) {
|
public AABB getRenderBoundingBox(MonitorBlockEntity monitor) {
|
||||||
return monitor.getRenderBoundingBox();
|
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.PoseStack;
|
||||||
import com.mojang.blaze3d.vertex.VertexConsumer;
|
import com.mojang.blaze3d.vertex.VertexConsumer;
|
||||||
|
import dan200.computercraft.client.render.BlockOutlineRenderer;
|
||||||
import dan200.computercraft.shared.peripheral.monitor.MonitorBlockEntity;
|
import dan200.computercraft.shared.peripheral.monitor.MonitorBlockEntity;
|
||||||
import net.minecraft.client.Camera;
|
import net.minecraft.client.Camera;
|
||||||
import net.minecraft.client.renderer.MultiBufferSource;
|
import net.minecraft.client.renderer.MultiBufferSource;
|
||||||
import net.minecraft.client.renderer.RenderType;
|
|
||||||
import net.minecraft.core.Direction;
|
import net.minecraft.core.Direction;
|
||||||
import net.minecraft.world.phys.BlockHitResult;
|
import net.minecraft.world.phys.BlockHitResult;
|
||||||
import org.joml.Matrix4f;
|
|
||||||
|
|
||||||
import java.util.EnumSet;
|
import java.util.EnumSet;
|
||||||
|
|
||||||
@ -33,8 +32,7 @@ public final class MonitorHighlightRenderer {
|
|||||||
var world = camera.getEntity().getCommandSenderWorld();
|
var world = camera.getEntity().getCommandSenderWorld();
|
||||||
var pos = hit.getBlockPos();
|
var pos = hit.getBlockPos();
|
||||||
|
|
||||||
var tile = world.getBlockEntity(pos);
|
if (!(world.getBlockEntity(pos) instanceof MonitorBlockEntity monitor)) return false;
|
||||||
if (!(tile instanceof MonitorBlockEntity monitor)) return false;
|
|
||||||
|
|
||||||
// Determine which sides are part of the external faces of the monitor, and so which need to be rendered.
|
// 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);
|
var faces = EnumSet.allOf(Direction.class);
|
||||||
@ -49,39 +47,37 @@ public final class MonitorHighlightRenderer {
|
|||||||
transformStack.pushPose();
|
transformStack.pushPose();
|
||||||
transformStack.translate(pos.getX() - cameraPos.x(), pos.getY() - cameraPos.y(), pos.getZ() - cameraPos.z());
|
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 transform = transformStack.last();
|
||||||
var buffer = bufferSource.getBuffer(RenderType.lines());
|
BlockOutlineRenderer.render(bufferSource, (buffer, colour) -> draw(buffer, transform, faces, colour));
|
||||||
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);
|
|
||||||
|
|
||||||
transformStack.popPose();
|
transformStack.popPose();
|
||||||
return true;
|
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
|
buffer
|
||||||
.addVertex(transform, x, y, z)
|
.addVertex(transform, x, y, z)
|
||||||
.setColor(0, 0, 0, 0.4f)
|
.setColor(colour)
|
||||||
.setNormal(normal, direction.getStepX(), direction.getStepY(), direction.getStepZ());
|
.setNormal(transform, direction.getStepX(), direction.getStepY(), direction.getStepZ());
|
||||||
buffer
|
buffer
|
||||||
.addVertex(transform,
|
.addVertex(transform, x + direction.getStepX(), y + direction.getStepY(), z + direction.getStepZ())
|
||||||
x + direction.getStepX(),
|
.setColor(colour)
|
||||||
y + direction.getStepY(),
|
.setNormal(transform, direction.getStepX(), direction.getStepY(), direction.getStepZ());
|
||||||
z + direction.getStepZ()
|
|
||||||
)
|
|
||||||
.setColor(0, 0, 0, 0.4f)
|
|
||||||
.setNormal(normal, direction.getStepX(), direction.getStepY(), direction.getStepZ());
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -5,17 +5,11 @@
|
|||||||
package dan200.computercraft.client.render.monitor;
|
package dan200.computercraft.client.render.monitor;
|
||||||
|
|
||||||
import com.google.errorprone.annotations.concurrent.GuardedBy;
|
import com.google.errorprone.annotations.concurrent.GuardedBy;
|
||||||
import com.mojang.blaze3d.platform.GlStateManager;
|
import com.mojang.blaze3d.buffers.BufferUsage;
|
||||||
import dan200.computercraft.client.render.vbo.DirectBuffers;
|
import com.mojang.blaze3d.vertex.VertexBuffer;
|
||||||
import dan200.computercraft.client.render.vbo.DirectVertexBuffer;
|
|
||||||
import dan200.computercraft.shared.peripheral.monitor.ClientMonitor;
|
import dan200.computercraft.shared.peripheral.monitor.ClientMonitor;
|
||||||
import dan200.computercraft.shared.peripheral.monitor.MonitorRenderer;
|
|
||||||
import net.minecraft.core.BlockPos;
|
import net.minecraft.core.BlockPos;
|
||||||
import org.jspecify.annotations.Nullable;
|
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.HashSet;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
@ -34,52 +28,23 @@ public class MonitorRenderState implements ClientMonitor.RenderState {
|
|||||||
public long lastRenderFrame = -1;
|
public long lastRenderFrame = -1;
|
||||||
public @Nullable BlockPos lastRenderPos = null;
|
public @Nullable BlockPos lastRenderPos = null;
|
||||||
|
|
||||||
public int tboBuffer;
|
public @Nullable VertexBuffer backgroundBuffer;
|
||||||
public int tboTexture;
|
public @Nullable VertexBuffer foregroundBuffer;
|
||||||
public int tboUniform;
|
|
||||||
public @Nullable DirectVertexBuffer backgroundBuffer;
|
|
||||||
public @Nullable DirectVertexBuffer foregroundBuffer;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create the appropriate buffer if needed.
|
* 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,
|
* @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.
|
* or this mode does not require one.
|
||||||
*/
|
*/
|
||||||
public boolean createBuffer(MonitorRenderer renderer) {
|
public boolean createBuffer() {
|
||||||
switch (renderer) {
|
if (backgroundBuffer != null) return false;
|
||||||
case TBO: {
|
|
||||||
if (tboBuffer != 0) return false;
|
|
||||||
|
|
||||||
deleteBuffers();
|
deleteBuffers();
|
||||||
|
backgroundBuffer = new VertexBuffer(BufferUsage.STATIC_WRITE);
|
||||||
tboBuffer = DirectBuffers.createBuffer();
|
foregroundBuffer = new VertexBuffer(BufferUsage.STATIC_WRITE);
|
||||||
DirectBuffers.setEmptyBufferData(GL31.GL_TEXTURE_BUFFER, tboBuffer, GL15.GL_STATIC_DRAW);
|
addMonitor();
|
||||||
tboTexture = GlStateManager._genTexture();
|
return true;
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void addMonitor() {
|
private void addMonitor() {
|
||||||
@ -89,21 +54,6 @@ public class MonitorRenderState implements ClientMonitor.RenderState {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void deleteBuffers() {
|
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) {
|
if (backgroundBuffer != null) {
|
||||||
backgroundBuffer.close();
|
backgroundBuffer.close();
|
||||||
backgroundBuffer = null;
|
backgroundBuffer = null;
|
||||||
@ -117,7 +67,7 @@ public class MonitorRenderState implements ClientMonitor.RenderState {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void close() {
|
public void close() {
|
||||||
if (tboBuffer != 0 || backgroundBuffer != null) {
|
if (backgroundBuffer != null) {
|
||||||
synchronized (allMonitors) {
|
synchronized (allMonitors) {
|
||||||
allMonitors.remove(this);
|
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;
|
package dan200.computercraft.client.render.text;
|
||||||
|
|
||||||
|
import com.mojang.blaze3d.vertex.ByteBufferBuilder;
|
||||||
import com.mojang.blaze3d.vertex.DefaultVertexFormat;
|
import com.mojang.blaze3d.vertex.DefaultVertexFormat;
|
||||||
import com.mojang.blaze3d.vertex.VertexConsumer;
|
import com.mojang.blaze3d.vertex.VertexConsumer;
|
||||||
import com.mojang.blaze3d.vertex.VertexFormat;
|
import com.mojang.blaze3d.vertex.VertexFormat;
|
||||||
import dan200.computercraft.client.render.RenderTypes;
|
|
||||||
import dan200.computercraft.core.terminal.Palette;
|
import dan200.computercraft.core.terminal.Palette;
|
||||||
import dan200.computercraft.core.terminal.Terminal;
|
import dan200.computercraft.core.terminal.Terminal;
|
||||||
import dan200.computercraft.core.terminal.TextBuffer;
|
import dan200.computercraft.core.terminal.TextBuffer;
|
||||||
import dan200.computercraft.core.util.Colour;
|
import dan200.computercraft.core.util.Colour;
|
||||||
import net.minecraft.util.FastColor;
|
import net.minecraft.util.ARGB;
|
||||||
import org.lwjgl.system.MemoryUtil;
|
import org.lwjgl.system.MemoryUtil;
|
||||||
|
|
||||||
import java.nio.ByteBuffer;
|
|
||||||
import java.nio.ByteOrder;
|
import java.nio.ByteOrder;
|
||||||
|
|
||||||
import static dan200.computercraft.client.render.text.FixedWidthFontRenderer.*;
|
import static dan200.computercraft.client.render.text.FixedWidthFontRenderer.*;
|
||||||
@ -168,40 +167,38 @@ public final class DirectFixedWidthFontRenderer {
|
|||||||
public interface QuadEmitter {
|
public interface QuadEmitter {
|
||||||
VertexFormat format();
|
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);
|
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
|
@Override
|
||||||
public VertexFormat format() {
|
public VertexFormat format() {
|
||||||
return RenderTypes.TERMINAL.format();
|
return TERMINAL_TEXT.format();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void quad(float x1, float y1, float x2, float y2, float z, int colour, float u1, float v1, float u2, float v2) {
|
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
|
// 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.
|
// 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.
|
// 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.
|
// which matches the POSITION_COLOR_TEX_LIGHTMAP vertex format.
|
||||||
|
var addr = builder.reserve(112);
|
||||||
var position = buffer.position();
|
|
||||||
var addr = MemoryUtil.memAddress(buffer);
|
|
||||||
|
|
||||||
// We're doing terrible unsafe hacks below, so let's be really sure that what we're doing is reasonable.
|
// 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.
|
// Require the pointer to be aligned to a 32-bit boundary.
|
||||||
if ((addr & 3) != 0) throw new IllegalStateException("Memory is not aligned");
|
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 = ARGB.toABGR(colour);
|
||||||
var colourAbgr = FastColor.ABGR32.fromArgb32(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);
|
var nativeColour = IS_LITTLE_ENDIAN ? colourAbgr : Integer.reverseBytes(colourAbgr);
|
||||||
|
|
||||||
memPutFloat(addr + 0, x1);
|
memPutFloat(addr + 0, x1);
|
||||||
@ -240,9 +237,6 @@ public final class DirectFixedWidthFontRenderer {
|
|||||||
memPutShort(addr + 108, (short) 0xF0);
|
memPutShort(addr + 108, (short) 0xF0);
|
||||||
memPutShort(addr + 110, (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.
|
// 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.Terminal;
|
||||||
import dan200.computercraft.core.terminal.TextBuffer;
|
import dan200.computercraft.core.terminal.TextBuffer;
|
||||||
import dan200.computercraft.core.util.Colour;
|
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.resources.ResourceLocation;
|
||||||
import net.minecraft.util.FastColor;
|
import net.minecraft.util.ARGB;
|
||||||
import org.joml.Matrix4f;
|
import org.joml.Matrix4f;
|
||||||
import org.joml.Vector3f;
|
import org.joml.Vector3f;
|
||||||
|
|
||||||
import static dan200.computercraft.client.render.RenderTypes.FULL_BRIGHT_LIGHTMAP;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Handles rendering fixed width text and computer terminals.
|
* Handles rendering fixed width text and computer terminals.
|
||||||
* <p>
|
* <p>
|
||||||
@ -33,7 +33,12 @@ import static dan200.computercraft.client.render.RenderTypes.FULL_BRIGHT_LIGHTMA
|
|||||||
* {@link DirectFixedWidthFontRenderer}.
|
* {@link DirectFixedWidthFontRenderer}.
|
||||||
*/
|
*/
|
||||||
public final class FixedWidthFontRenderer {
|
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_HEIGHT = 9;
|
||||||
public static final int FONT_WIDTH = 6;
|
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_START = (WIDTH - 6.0f) / WIDTH;
|
||||||
static final float BACKGROUND_END = (WIDTH - 4.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 static final float Z_OFFSET = 1e-4f;
|
||||||
|
|
||||||
private FixedWidthFontRenderer() {
|
private FixedWidthFontRenderer() {
|
||||||
@ -137,7 +142,7 @@ public final class FixedWidthFontRenderer {
|
|||||||
var rowY = y + FONT_HEIGHT * i;
|
var rowY = y + FONT_HEIGHT * i;
|
||||||
drawString(
|
drawString(
|
||||||
emitter, x, rowY, terminal.getLine(i), terminal.getTextColourLine(i),
|
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
|
// Top and bottom margins
|
||||||
drawBackground(
|
drawBackground(
|
||||||
emitter, x, y - topMarginSize, terminal.getBackgroundColourLine(0), palette,
|
emitter, x, y - topMarginSize, terminal.getBackgroundColourLine(0), palette,
|
||||||
leftMarginSize, rightMarginSize, topMarginSize, FULL_BRIGHT_LIGHTMAP
|
leftMarginSize, rightMarginSize, topMarginSize, LightTexture.FULL_BRIGHT
|
||||||
);
|
);
|
||||||
|
|
||||||
drawBackground(
|
drawBackground(
|
||||||
emitter, x, y + height * FONT_HEIGHT, terminal.getBackgroundColourLine(height - 1), palette,
|
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
|
// The main text
|
||||||
@ -165,7 +170,7 @@ public final class FixedWidthFontRenderer {
|
|||||||
var rowY = y + FONT_HEIGHT * i;
|
var rowY = y + FONT_HEIGHT * i;
|
||||||
drawBackground(
|
drawBackground(
|
||||||
emitter, x, rowY, terminal.getBackgroundColourLine(i), palette,
|
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) {
|
public static void drawCursor(QuadEmitter emitter, float x, float y, Terminal terminal) {
|
||||||
if (isCursorVisible(terminal) && FrameInfo.getGlobalCursorBlink()) {
|
if (isCursorVisible(terminal) && FrameInfo.getGlobalCursorBlink()) {
|
||||||
var colour = terminal.getPalette().getRenderColours(15 - terminal.getTextColour());
|
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) {
|
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) {
|
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) {
|
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 poseMatrix = c.poseMatrix();
|
||||||
var consumer = c.consumer();
|
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, y1, z).setColor(colour).setUv(u1, v1).setLight(light);
|
||||||
consumer.addVertex(poseMatrix, x1, y2, z).setColor(r, g, b, a).setUv(u1, v2).setLight(light);
|
consumer.addVertex(poseMatrix, x1, y2, z).setColor(colour).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, y2, z).setColor(colour).setUv(u2, v2).setLight(light);
|
||||||
consumer.addVertex(poseMatrix, x2, y1, z).setColor(r, g, b, a).setUv(u2, v1).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;
|
package dan200.computercraft.client.turtle;
|
||||||
|
|
||||||
import dan200.computercraft.api.ComputerCraftAPI;
|
import dan200.computercraft.api.ComputerCraftAPI;
|
||||||
import dan200.computercraft.api.client.ModelLocation;
|
|
||||||
import dan200.computercraft.api.client.TransformedModel;
|
import dan200.computercraft.api.client.TransformedModel;
|
||||||
import dan200.computercraft.api.client.turtle.TurtleUpgradeModeller;
|
import dan200.computercraft.api.client.turtle.TurtleUpgradeModeller;
|
||||||
import dan200.computercraft.api.turtle.ITurtleAccess;
|
import dan200.computercraft.api.turtle.ITurtleAccess;
|
||||||
@ -39,23 +38,23 @@ public class TurtleModemModeller implements TurtleUpgradeModeller<TurtleModem> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private record ModemModels(
|
private record ModemModels(
|
||||||
ModelLocation leftOffModel, ModelLocation rightOffModel,
|
ResourceLocation leftOffModel, ResourceLocation rightOffModel,
|
||||||
ModelLocation leftOnModel, ModelLocation rightOnModel
|
ResourceLocation leftOnModel, ResourceLocation rightOnModel
|
||||||
) {
|
) {
|
||||||
private static final ModemModels NORMAL = create("normal");
|
private static final ModemModels NORMAL = create("normal");
|
||||||
private static final ModemModels ADVANCED = create("advanced");
|
private static final ModemModels ADVANCED = create("advanced");
|
||||||
|
|
||||||
public static ModemModels create(String type) {
|
public static ModemModels create(String type) {
|
||||||
return new ModemModels(
|
return new ModemModels(
|
||||||
ModelLocation.ofResource(ResourceLocation.fromNamespaceAndPath(ComputerCraftAPI.MOD_ID, "block/turtle_modem_" + type + "_off_left")),
|
ResourceLocation.fromNamespaceAndPath(ComputerCraftAPI.MOD_ID, "block/turtle_modem_" + type + "_off_left"),
|
||||||
ModelLocation.ofResource(ResourceLocation.fromNamespaceAndPath(ComputerCraftAPI.MOD_ID, "block/turtle_modem_" + type + "_off_right")),
|
ResourceLocation.fromNamespaceAndPath(ComputerCraftAPI.MOD_ID, "block/turtle_modem_" + type + "_off_right"),
|
||||||
ModelLocation.ofResource(ResourceLocation.fromNamespaceAndPath(ComputerCraftAPI.MOD_ID, "block/turtle_modem_" + type + "_on_left")),
|
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 + "_on_right")
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
public Stream<ResourceLocation> getDependencies() {
|
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;
|
package dan200.computercraft.client.turtle;
|
||||||
|
|
||||||
import com.mojang.math.Transformation;
|
|
||||||
import dan200.computercraft.api.client.TransformedModel;
|
import dan200.computercraft.api.client.TransformedModel;
|
||||||
import dan200.computercraft.api.client.turtle.TurtleUpgradeModeller;
|
import dan200.computercraft.api.client.turtle.TurtleUpgradeModeller;
|
||||||
import dan200.computercraft.api.turtle.ITurtleAccess;
|
import dan200.computercraft.api.turtle.ITurtleAccess;
|
||||||
@ -25,7 +24,7 @@ import java.util.stream.Stream;
|
|||||||
*/
|
*/
|
||||||
public final class TurtleUpgradeModellers {
|
public final class TurtleUpgradeModellers {
|
||||||
private static final TurtleUpgradeModeller<ITurtleUpgrade> NULL_TURTLE_MODELLER = (upgrade, turtle, side, data) ->
|
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 final Map<UpgradeType<? extends ITurtleUpgrade>, TurtleUpgradeModeller<?>> turtleModels = new ConcurrentHashMap<>();
|
||||||
private static volatile boolean fetchedModels;
|
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.gui.GuiSprites;
|
||||||
import dan200.computercraft.client.model.LecternPocketModel;
|
import dan200.computercraft.client.model.LecternPocketModel;
|
||||||
import dan200.computercraft.client.model.LecternPrintoutModel;
|
import dan200.computercraft.client.model.LecternPrintoutModel;
|
||||||
|
import dan200.computercraft.data.client.BlockModelProvider;
|
||||||
import dan200.computercraft.data.client.ExtraModelsProvider;
|
import dan200.computercraft.data.client.ExtraModelsProvider;
|
||||||
|
import dan200.computercraft.data.client.ItemModelProvider;
|
||||||
import dan200.computercraft.shared.turtle.TurtleOverlay;
|
import dan200.computercraft.shared.turtle.TurtleOverlay;
|
||||||
import dan200.computercraft.shared.turtle.inventory.UpgradeSlot;
|
import dan200.computercraft.shared.turtle.inventory.UpgradeSlot;
|
||||||
import net.minecraft.Util;
|
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.SpriteSource;
|
||||||
import net.minecraft.client.renderer.texture.atlas.SpriteSources;
|
import net.minecraft.client.renderer.texture.atlas.SpriteSources;
|
||||||
import net.minecraft.client.renderer.texture.atlas.sources.SingleFile;
|
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.registries.RegistryPatchGenerator;
|
||||||
import net.minecraft.data.tags.TagsProvider;
|
import net.minecraft.data.tags.TagsProvider;
|
||||||
import net.minecraft.resources.ResourceLocation;
|
import net.minecraft.resources.ResourceLocation;
|
||||||
import net.minecraft.server.packs.PackType;
|
|
||||||
import net.minecraft.world.item.Item;
|
import net.minecraft.world.item.Item;
|
||||||
import net.minecraft.world.level.block.Block;
|
import net.minecraft.world.level.block.Block;
|
||||||
|
|
||||||
@ -57,25 +60,27 @@ public final class DataProviders {
|
|||||||
var fullRegistries = fullRegistryPatch.thenApply(RegistrySetBuilder.PatchedRegistries::full);
|
var fullRegistries = fullRegistryPatch.thenApply(RegistrySetBuilder.PatchedRegistries::full);
|
||||||
|
|
||||||
generator.registries(fullRegistryPatch);
|
generator.registries(fullRegistryPatch);
|
||||||
generator.add(out -> new RecipeProvider(out, fullRegistries));
|
generator.add(out -> new RecipeProvider.Runner(out, fullRegistries));
|
||||||
|
|
||||||
var blockTags = generator.blockTags(TagProvider::blockTags);
|
var blockTags = generator.blockTags(TagProvider::blockTags);
|
||||||
generator.itemTags(TagProvider::itemTags, 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 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.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(
|
out.accept(ResourceLocation.withDefaultNamespace("blocks"), makeSprites(Stream.of(
|
||||||
UpgradeSlot.LEFT_UPGRADE,
|
|
||||||
UpgradeSlot.RIGHT_UPGRADE,
|
|
||||||
LecternPrintoutModel.TEXTURE,
|
LecternPrintoutModel.TEXTURE,
|
||||||
LecternPocketModel.TEXTURE_NORMAL, LecternPocketModel.TEXTURE_ADVANCED,
|
LecternPocketModel.TEXTURE_NORMAL, LecternPocketModel.TEXTURE_ADVANCED,
|
||||||
LecternPocketModel.TEXTURE_COLOUR, LecternPocketModel.TEXTURE_FRAME, LecternPocketModel.TEXTURE_LIGHT
|
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(
|
out.accept(GuiSprites.SPRITE_SHEET, makeSprites(
|
||||||
// Computers
|
// Computers
|
||||||
GuiSprites.COMPUTER_NORMAL.textures(),
|
GuiSprites.COMPUTER_NORMAL.textures(),
|
||||||
@ -91,6 +96,8 @@ public final class DataProviders {
|
|||||||
return registries.lookupOrThrow(TurtleOverlay.REGISTRY).listElements().map(x -> x.value().model());
|
return registries.lookupOrThrow(TurtleOverlay.REGISTRY).listElements().map(x -> x.value().model());
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
generator.addModels(BlockModelProvider::addBlockModels, ItemModelProvider::addItemModels);
|
||||||
}
|
}
|
||||||
|
|
||||||
@SafeVarargs
|
@SafeVarargs
|
||||||
@ -104,7 +111,7 @@ public final class DataProviders {
|
|||||||
|
|
||||||
<T extends DataProvider> T add(DataProvider.Factory<T> factory);
|
<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);
|
TagsProvider<Block> blockTags(Consumer<TagProvider.TagConsumer<Block>> tags);
|
||||||
|
|
||||||
@ -116,5 +123,14 @@ public final class DataProviders {
|
|||||||
* @param registries The patched registries to write.
|
* @param registries The patched registries to write.
|
||||||
*/
|
*/
|
||||||
void registries(CompletableFuture<RegistrySetBuilder.PatchedRegistries> registries);
|
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.monitorWidth, "Max monitor width");
|
||||||
addConfigEntry(ConfigSpec.monitorHeight, "Max monitor height");
|
addConfigEntry(ConfigSpec.monitorHeight, "Max monitor height");
|
||||||
|
|
||||||
addConfigEntry(ConfigSpec.monitorRenderer, "Monitor renderer");
|
|
||||||
addConfigEntry(ConfigSpec.monitorDistance, "Monitor distance");
|
addConfigEntry(ConfigSpec.monitorDistance, "Monitor distance");
|
||||||
addConfigEntry(ConfigSpec.uploadNagDelay, "Upload nag delay");
|
addConfigEntry(ConfigSpec.uploadNagDelay, "Upload nag delay");
|
||||||
}
|
}
|
||||||
|
|
||||||
private Stream<String> getExpectedKeys(HolderLookup.Provider registries) {
|
private Stream<String> getExpectedKeys(HolderLookup.Provider registries) {
|
||||||
return Stream.of(
|
return Stream.of(
|
||||||
BuiltInRegistries.BLOCK.holders()
|
BuiltInRegistries.BLOCK.listElements()
|
||||||
.filter(x -> x.key().location().getNamespace().equals(ComputerCraftAPI.MOD_ID))
|
.filter(x -> x.key().location().getNamespace().equals(ComputerCraftAPI.MOD_ID))
|
||||||
.map(x -> x.value().getDescriptionId())
|
.map(x -> x.value().getDescriptionId())
|
||||||
// Exclude blocks that just reuse vanilla translations, such as the lectern.
|
// Exclude blocks that just reuse vanilla translations, such as the lectern.
|
||||||
.filter(x -> !x.startsWith("block.minecraft.")),
|
.filter(x -> !x.startsWith("block.minecraft.")),
|
||||||
BuiltInRegistries.ITEM.holders()
|
BuiltInRegistries.ITEM.listElements()
|
||||||
.filter(x -> x.key().location().getNamespace().equals(ComputerCraftAPI.MOD_ID))
|
.filter(x -> x.key().location().getNamespace().equals(ComputerCraftAPI.MOD_ID))
|
||||||
.map(x -> x.value().getDescriptionId()),
|
.map(x -> x.value().getDescriptionId()),
|
||||||
registries.lookupOrThrow(ITurtleUpgrade.REGISTRY).listElements().flatMap(x -> getTranslationKeys(x.value().getAdjective())),
|
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());
|
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()
|
.lootTable()
|
||||||
.withPool(LootPool.lootPool()
|
.withPool(LootPool.lootPool()
|
||||||
.setRolls(ConstantValue.exactly(1))
|
.setRolls(ConstantValue.exactly(1))
|
||||||
@ -115,7 +115,7 @@ class LootTableProvider {
|
|||||||
LootItemCondition.Builder condition
|
LootItemCondition.Builder condition
|
||||||
) {
|
) {
|
||||||
var block = wrapper.get();
|
var block = wrapper.get();
|
||||||
add.accept(block.getLootTable(), LootTable
|
add.accept(block.getLootTable().orElseThrow(), LootTable
|
||||||
.lootTable()
|
.lootTable()
|
||||||
.withPool(LootPool.lootPool()
|
.withPool(LootPool.lootPool()
|
||||||
.setRolls(ConstantValue.exactly(1))
|
.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;
|
package dan200.computercraft.data;
|
||||||
|
|
||||||
import com.google.gson.JsonObject;
|
|
||||||
import com.mojang.authlib.GameProfile;
|
import com.mojang.authlib.GameProfile;
|
||||||
import com.mojang.serialization.JsonOps;
|
|
||||||
import dan200.computercraft.api.ComputerCraftAPI;
|
import dan200.computercraft.api.ComputerCraftAPI;
|
||||||
import dan200.computercraft.api.pocket.IPocketUpgrade;
|
import dan200.computercraft.api.pocket.IPocketUpgrade;
|
||||||
import dan200.computercraft.api.turtle.ITurtleUpgrade;
|
import dan200.computercraft.api.turtle.ITurtleUpgrade;
|
||||||
import dan200.computercraft.api.upgrades.UpgradeData;
|
import dan200.computercraft.api.upgrades.UpgradeData;
|
||||||
import dan200.computercraft.core.util.Colour;
|
|
||||||
import dan200.computercraft.data.recipe.ShapedSpecBuilder;
|
import dan200.computercraft.data.recipe.ShapedSpecBuilder;
|
||||||
import dan200.computercraft.data.recipe.ShapelessSpecBuilder;
|
import dan200.computercraft.data.recipe.ShapelessSpecBuilder;
|
||||||
import dan200.computercraft.shared.ModRegistry;
|
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.items.PocketComputerItem;
|
||||||
import dan200.computercraft.shared.pocket.recipes.PocketComputerUpgradeRecipe;
|
import dan200.computercraft.shared.pocket.recipes.PocketComputerUpgradeRecipe;
|
||||||
import dan200.computercraft.shared.recipe.ImpostorShapedRecipe;
|
import dan200.computercraft.shared.recipe.ImpostorShapedRecipe;
|
||||||
import dan200.computercraft.shared.recipe.ImpostorShapelessRecipe;
|
|
||||||
import dan200.computercraft.shared.recipe.TransformShapedRecipe;
|
import dan200.computercraft.shared.recipe.TransformShapedRecipe;
|
||||||
import dan200.computercraft.shared.recipe.TransformShapelessRecipe;
|
import dan200.computercraft.shared.recipe.TransformShapelessRecipe;
|
||||||
import dan200.computercraft.shared.recipe.function.CopyComponents;
|
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.Criterion;
|
||||||
import net.minecraft.advancements.critereon.InventoryChangeTrigger;
|
import net.minecraft.advancements.critereon.InventoryChangeTrigger;
|
||||||
import net.minecraft.advancements.critereon.ItemPredicate;
|
import net.minecraft.advancements.critereon.ItemPredicate;
|
||||||
|
import net.minecraft.core.HolderGetter;
|
||||||
import net.minecraft.core.HolderLookup;
|
import net.minecraft.core.HolderLookup;
|
||||||
import net.minecraft.core.component.DataComponents;
|
import net.minecraft.core.component.DataComponents;
|
||||||
import net.minecraft.core.registries.BuiltInRegistries;
|
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.PackOutput;
|
||||||
import net.minecraft.data.recipes.RecipeCategory;
|
import net.minecraft.data.recipes.RecipeCategory;
|
||||||
import net.minecraft.data.recipes.RecipeOutput;
|
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.ResourceKey;
|
||||||
import net.minecraft.resources.ResourceLocation;
|
import net.minecraft.resources.ResourceLocation;
|
||||||
import net.minecraft.tags.ItemTags;
|
import net.minecraft.tags.ItemTags;
|
||||||
import net.minecraft.tags.TagKey;
|
import net.minecraft.tags.TagKey;
|
||||||
import net.minecraft.util.GsonHelper;
|
import net.minecraft.world.item.DyeColor;
|
||||||
import net.minecraft.world.item.*;
|
import net.minecraft.world.item.Item;
|
||||||
import net.minecraft.world.item.component.DyedItemColor;
|
import net.minecraft.world.item.ItemStack;
|
||||||
|
import net.minecraft.world.item.Items;
|
||||||
import net.minecraft.world.item.component.ResolvableProfile;
|
import net.minecraft.world.item.component.ResolvableProfile;
|
||||||
import net.minecraft.world.item.crafting.CraftingBookCategory;
|
import net.minecraft.world.item.crafting.CraftingBookCategory;
|
||||||
import net.minecraft.world.item.crafting.Ingredient;
|
import net.minecraft.world.item.crafting.Ingredient;
|
||||||
@ -62,68 +58,46 @@ import net.minecraft.world.level.ItemLike;
|
|||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
import java.util.concurrent.CompletableFuture;
|
import java.util.concurrent.CompletableFuture;
|
||||||
import java.util.concurrent.ExecutionException;
|
|
||||||
import java.util.function.Consumer;
|
import java.util.function.Consumer;
|
||||||
|
|
||||||
import static dan200.computercraft.api.ComputerCraftTags.Items.COMPUTER;
|
import static dan200.computercraft.api.ComputerCraftTags.Items.COMPUTER;
|
||||||
import static dan200.computercraft.api.ComputerCraftTags.Items.WIRED_MODEM;
|
import static dan200.computercraft.api.ComputerCraftTags.Items.WIRED_MODEM;
|
||||||
|
|
||||||
final class RecipeProvider extends net.minecraft.data.recipes.RecipeProvider {
|
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(HolderLookup.Provider registries, RecipeOutput recipeOutput) {
|
||||||
|
super(registries, recipeOutput);
|
||||||
RecipeProvider(PackOutput output, CompletableFuture<HolderLookup.Provider> registries) {
|
this.items = registries.lookupOrThrow(Registries.ITEM);
|
||||||
super(output, registries);
|
ingredients = PlatformHelper.get().getRecipeIngredients();
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void buildRecipes(RecipeOutput add) {
|
public void buildRecipes() {
|
||||||
var registries = registries();
|
basicRecipes();
|
||||||
|
diskColours();
|
||||||
|
pocketUpgrades();
|
||||||
|
turtleUpgrades();
|
||||||
|
turtleOverlays();
|
||||||
|
|
||||||
basicRecipes(add);
|
special(new ColourableRecipe(CraftingBookCategory.MISC));
|
||||||
diskColours(add);
|
special(new ClearColourRecipe(CraftingBookCategory.MISC));
|
||||||
pocketUpgrades(add, registries);
|
special(new TurtleUpgradeRecipe(CraftingBookCategory.MISC));
|
||||||
turtleUpgrades(add, registries);
|
special(new PocketComputerUpgradeRecipe(CraftingBookCategory.MISC));
|
||||||
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));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Register a crafting recipe for a disk of every dye colour.
|
* Register a disk recipe.
|
||||||
*
|
|
||||||
* @param output The callback to add recipes.
|
|
||||||
*/
|
*/
|
||||||
private void diskColours(RecipeOutput output) {
|
private void diskColours() {
|
||||||
for (var colour : Colour.VALUES) {
|
customShapeless(RecipeCategory.REDSTONE, ModRegistry.Items.DISK.get())
|
||||||
ShapelessSpecBuilder
|
.requires(ingredients.redstone())
|
||||||
.shapeless(RecipeCategory.REDSTONE, DataComponentUtil.createStack(ModRegistry.Items.DISK.get(), DataComponents.DYED_COLOR, new DyedItemColor(colour.getHex(), false)))
|
.requires(Items.PAPER)
|
||||||
.requires(ingredients.redstone())
|
.group("computercraft:disk")
|
||||||
.requires(Items.PAPER)
|
.unlockedBy("has_drive", has(ModRegistry.Items.DISK_DRIVE.get()))
|
||||||
.requires(DyeItem.byColor(ofColour(colour)))
|
.build(d -> new DiskRecipe(d.properties(), d.ingredients()))
|
||||||
.group("computercraft:disk")
|
.save(output, ResourceLocation.fromNamespaceAndPath(ComputerCraftAPI.MOD_ID, "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 static List<TurtleItem> turtleItems() {
|
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.
|
* 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()) {
|
for (var turtleItem : turtleItems()) {
|
||||||
var name = RegistryHelper.getKeyOrThrow(BuiltInRegistries.ITEM, turtleItem);
|
var name = RegistryHelper.getKeyOrThrow(BuiltInRegistries.ITEM, turtleItem);
|
||||||
|
|
||||||
registries.lookupOrThrow(ITurtleUpgrade.REGISTRY).listElements().forEach(upgradeHolder -> {
|
registries.lookupOrThrow(ITurtleUpgrade.REGISTRY).listElements().forEach(upgradeHolder -> {
|
||||||
var upgrade = upgradeHolder.value();
|
var upgrade = upgradeHolder.value();
|
||||||
ShapedSpecBuilder
|
customShaped(RecipeCategory.REDSTONE, DataComponentUtil.createStack(turtleItem, ModRegistry.DataComponents.RIGHT_TURTLE_UPGRADE.get(), UpgradeData.ofDefault(upgradeHolder)))
|
||||||
.shaped(RecipeCategory.REDSTONE, DataComponentUtil.createStack(turtleItem, ModRegistry.DataComponents.RIGHT_TURTLE_UPGRADE.get(), UpgradeData.ofDefault(upgradeHolder)))
|
|
||||||
.group(name.toString())
|
.group(name.toString())
|
||||||
.pattern("#T")
|
.pattern("#T")
|
||||||
.define('T', turtleItem)
|
.define('T', turtleItem)
|
||||||
.define('#', upgrade.getCraftingItem().getItem())
|
.define('#', upgrade.getCraftingItem().getItem())
|
||||||
.unlockedBy("has_items", inventoryChange(turtleItem, upgrade.getCraftingItem().getItem()))
|
.unlockedBy("has_items", has(turtleItem, upgrade.getCraftingItem().getItem()))
|
||||||
.build(ImpostorShapedRecipe::new)
|
.build(ImpostorShapedRecipe::new)
|
||||||
.save(
|
.save(
|
||||||
add,
|
output,
|
||||||
name.withSuffix(String.format("/%s/%s", upgradeHolder.key().location().getNamespace(), upgradeHolder.key().location().getPath()))
|
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.
|
* 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()) {
|
for (var pocket : pocketComputerItems()) {
|
||||||
var name = RegistryHelper.getKeyOrThrow(BuiltInRegistries.ITEM, pocket).withPath(x -> x.replace("pocket_computer_", "pocket_"));
|
var name = RegistryHelper.getKeyOrThrow(BuiltInRegistries.ITEM, pocket).withPath(x -> x.replace("pocket_computer_", "pocket_"));
|
||||||
|
|
||||||
registries.lookupOrThrow(IPocketUpgrade.REGISTRY).listElements().forEach(upgradeHolder -> {
|
registries.lookupOrThrow(IPocketUpgrade.REGISTRY).listElements().forEach(upgradeHolder -> {
|
||||||
var upgrade = upgradeHolder.value();
|
var upgrade = upgradeHolder.value();
|
||||||
ShapedSpecBuilder
|
customShaped(RecipeCategory.REDSTONE, DataComponentUtil.createStack(pocket, ModRegistry.DataComponents.POCKET_UPGRADE.get(), UpgradeData.ofDefault(upgradeHolder)))
|
||||||
.shaped(RecipeCategory.REDSTONE, DataComponentUtil.createStack(pocket, ModRegistry.DataComponents.POCKET_UPGRADE.get(), UpgradeData.ofDefault(upgradeHolder)))
|
|
||||||
.group(name.toString())
|
.group(name.toString())
|
||||||
.pattern("#")
|
.pattern("#")
|
||||||
.pattern("P")
|
.pattern("P")
|
||||||
.define('P', pocket)
|
.define('P', pocket)
|
||||||
.define('#', upgrade.getCraftingItem().getItem())
|
.define('#', upgrade.getCraftingItem().getItem())
|
||||||
.unlockedBy("has_items", inventoryChange(pocket, upgrade.getCraftingItem().getItem()))
|
.unlockedBy("has_items", has(pocket, upgrade.getCraftingItem().getItem()))
|
||||||
.build(ImpostorShapedRecipe::new)
|
.build(ImpostorShapedRecipe::new)
|
||||||
.save(
|
.save(
|
||||||
add,
|
output,
|
||||||
name.withSuffix(String.format("/%s/%s", upgradeHolder.key().location().getNamespace(), upgradeHolder.key().location().getPath()))
|
name.withSuffix(String.format("/%s/%s", upgradeHolder.key().location().getNamespace(), upgradeHolder.key().location().getPath()))
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void turtleOverlays(RecipeOutput add, HolderLookup.Provider registries) {
|
private void turtleOverlays() {
|
||||||
turtleOverlay(add, registries, TurtleOverlays.TRANS_FLAG, x -> x
|
turtleOverlay(TurtleOverlays.TRANS_FLAG, x -> x
|
||||||
.unlockedBy("has_dye", inventoryChange(itemPredicate(ingredients.dye())))
|
.unlockedBy("has_dye", has(ingredients.dye()))
|
||||||
.requires(ColourUtils.getDyeTag(DyeColor.LIGHT_BLUE))
|
.requires(ColourUtils.getDyeTag(DyeColor.LIGHT_BLUE))
|
||||||
.requires(ColourUtils.getDyeTag(DyeColor.PINK))
|
.requires(ColourUtils.getDyeTag(DyeColor.PINK))
|
||||||
.requires(ColourUtils.getDyeTag(DyeColor.WHITE))
|
.requires(ColourUtils.getDyeTag(DyeColor.WHITE))
|
||||||
.requires(Items.STICK)
|
.requires(Items.STICK)
|
||||||
);
|
);
|
||||||
|
|
||||||
turtleOverlay(add, registries, TurtleOverlays.RAINBOW_FLAG, x -> x
|
turtleOverlay(TurtleOverlays.RAINBOW_FLAG, x -> x
|
||||||
.unlockedBy("has_dye", inventoryChange(itemPredicate(ingredients.dye())))
|
.unlockedBy("has_dye", has(ingredients.dye()))
|
||||||
.requires(ColourUtils.getDyeTag(DyeColor.RED))
|
.requires(ColourUtils.getDyeTag(DyeColor.RED))
|
||||||
.requires(ColourUtils.getDyeTag(DyeColor.ORANGE))
|
.requires(ColourUtils.getDyeTag(DyeColor.ORANGE))
|
||||||
.requires(ColourUtils.getDyeTag(DyeColor.YELLOW))
|
.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);
|
var holder = registries.lookupOrThrow(overlay.registryKey()).getOrThrow(overlay);
|
||||||
|
|
||||||
for (var turtleItem : turtleItems()) {
|
for (var turtleItem : turtleItems()) {
|
||||||
var name = RegistryHelper.getKeyOrThrow(BuiltInRegistries.ITEM, turtleItem);
|
var name = RegistryHelper.getKeyOrThrow(BuiltInRegistries.ITEM, turtleItem);
|
||||||
|
|
||||||
var builder = ShapelessSpecBuilder
|
var builder = customShapeless(RecipeCategory.REDSTONE, DataComponentUtil.createStack(turtleItem, ModRegistry.DataComponents.OVERLAY.get(), holder))
|
||||||
.shapeless(RecipeCategory.REDSTONE, DataComponentUtil.createStack(turtleItem, ModRegistry.DataComponents.OVERLAY.get(), holder))
|
|
||||||
.group(name.withSuffix("_overlay").toString())
|
.group(name.withSuffix("_overlay").toString())
|
||||||
.unlockedBy("has_turtle", inventoryChange(turtleItem));
|
.unlockedBy("has_turtle", has(turtleItem));
|
||||||
build.accept(builder);
|
build.accept(builder);
|
||||||
builder
|
builder
|
||||||
.requires(turtleItem)
|
.requires(turtleItem)
|
||||||
.build(s -> new TransformShapelessRecipe(s, List.of(
|
.build(s -> new TransformShapelessRecipe(s, List.of(
|
||||||
CopyComponents.builder(turtleItem).exclude(ModRegistry.DataComponents.OVERLAY.get()).build()
|
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) {
|
private void basicRecipes() {
|
||||||
ShapedRecipeBuilder
|
shaped(RecipeCategory.REDSTONE, ModRegistry.Items.CABLE.get(), 6)
|
||||||
.shaped(RecipeCategory.REDSTONE, ModRegistry.Items.CABLE.get(), 6)
|
|
||||||
.pattern(" # ")
|
.pattern(" # ")
|
||||||
.pattern("#R#")
|
.pattern("#R#")
|
||||||
.pattern(" # ")
|
.pattern(" # ")
|
||||||
.define('#', Items.STONE)
|
.define('#', Items.STONE)
|
||||||
.define('R', ingredients.redstone())
|
.define('R', ingredients.redstone())
|
||||||
.unlockedBy("has_computer", inventoryChange(COMPUTER))
|
.unlockedBy("has_computer", has(COMPUTER))
|
||||||
.unlockedBy("has_modem", inventoryChange(WIRED_MODEM))
|
.unlockedBy("has_modem", has(WIRED_MODEM))
|
||||||
.save(add);
|
.save(output);
|
||||||
|
|
||||||
ShapedRecipeBuilder
|
shaped(RecipeCategory.REDSTONE, ModRegistry.Items.COMPUTER_NORMAL.get())
|
||||||
.shaped(RecipeCategory.REDSTONE, ModRegistry.Items.COMPUTER_NORMAL.get())
|
|
||||||
.pattern("###")
|
.pattern("###")
|
||||||
.pattern("#R#")
|
.pattern("#R#")
|
||||||
.pattern("#G#")
|
.pattern("#G#")
|
||||||
.define('#', Items.STONE)
|
.define('#', Items.STONE)
|
||||||
.define('R', ingredients.redstone())
|
.define('R', ingredients.redstone())
|
||||||
.define('G', ingredients.glassPane())
|
.define('G', ingredients.glassPane())
|
||||||
.unlockedBy("has_redstone", inventoryChange(itemPredicate(ingredients.redstone())))
|
.unlockedBy("has_redstone", has(ingredients.redstone()))
|
||||||
.save(add);
|
.save(output);
|
||||||
|
|
||||||
ShapedRecipeBuilder
|
shaped(RecipeCategory.REDSTONE, ModRegistry.Items.COMPUTER_ADVANCED.get())
|
||||||
.shaped(RecipeCategory.REDSTONE, ModRegistry.Items.COMPUTER_ADVANCED.get())
|
|
||||||
.pattern("###")
|
.pattern("###")
|
||||||
.pattern("#R#")
|
.pattern("#R#")
|
||||||
.pattern("#G#")
|
.pattern("#G#")
|
||||||
.define('#', ingredients.goldIngot())
|
.define('#', ingredients.goldIngot())
|
||||||
.define('R', ingredients.redstone())
|
.define('R', ingredients.redstone())
|
||||||
.define('G', ingredients.glassPane())
|
.define('G', ingredients.glassPane())
|
||||||
.unlockedBy("has_components", inventoryChange(itemPredicate(ingredients.redstone()), itemPredicate(ingredients.goldIngot())))
|
.unlockedBy("has_components", inventoryTrigger(itemPredicate(ingredients.redstone()), itemPredicate(ingredients.goldIngot())))
|
||||||
.save(add);
|
.save(output);
|
||||||
|
|
||||||
ShapedSpecBuilder
|
customShaped(RecipeCategory.REDSTONE, ModRegistry.Items.COMPUTER_ADVANCED.get())
|
||||||
.shaped(RecipeCategory.REDSTONE, ModRegistry.Items.COMPUTER_ADVANCED.get())
|
|
||||||
.pattern("###")
|
.pattern("###")
|
||||||
.pattern("#C#")
|
.pattern("#C#")
|
||||||
.pattern("# #")
|
.pattern("# #")
|
||||||
.define('#', ingredients.goldIngot())
|
.define('#', ingredients.goldIngot())
|
||||||
.define('C', ModRegistry.Items.COMPUTER_NORMAL.get())
|
.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()))))
|
.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("###")
|
||||||
.pattern("#R#")
|
.pattern("#R#")
|
||||||
.pattern("#G#")
|
.pattern("#G#")
|
||||||
.define('#', ingredients.goldIngot())
|
.define('#', ingredients.goldIngot())
|
||||||
.define('R', Items.COMMAND_BLOCK)
|
.define('R', Items.COMMAND_BLOCK)
|
||||||
.define('G', ingredients.glassPane())
|
.define('G', ingredients.glassPane())
|
||||||
.unlockedBy("has_components", inventoryChange(Items.COMMAND_BLOCK))
|
.unlockedBy("has_components", has(Items.COMMAND_BLOCK))
|
||||||
.save(add);
|
.save(output);
|
||||||
|
|
||||||
ShapedSpecBuilder
|
customShaped(RecipeCategory.REDSTONE, ModRegistry.Items.TURTLE_NORMAL.get())
|
||||||
.shaped(RecipeCategory.REDSTONE, ModRegistry.Items.TURTLE_NORMAL.get())
|
|
||||||
.pattern("###")
|
.pattern("###")
|
||||||
.pattern("#C#")
|
.pattern("#C#")
|
||||||
.pattern("#I#")
|
.pattern("#I#")
|
||||||
.define('#', ingredients.ironIngot())
|
.define('#', ingredients.ironIngot())
|
||||||
.define('C', ModRegistry.Items.COMPUTER_NORMAL.get())
|
.define('C', ModRegistry.Items.COMPUTER_NORMAL.get())
|
||||||
.define('I', ingredients.woodenChest())
|
.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()))))
|
.build(x -> new TransformShapedRecipe(x, List.of(new CopyComponents(ModRegistry.Items.COMPUTER_NORMAL.get()))))
|
||||||
.save(add);
|
.save(output);
|
||||||
|
|
||||||
ShapedSpecBuilder
|
customShaped(RecipeCategory.REDSTONE, ModRegistry.Items.TURTLE_ADVANCED.get())
|
||||||
.shaped(RecipeCategory.REDSTONE, ModRegistry.Items.TURTLE_ADVANCED.get())
|
|
||||||
.pattern("###")
|
.pattern("###")
|
||||||
.pattern("#C#")
|
.pattern("#C#")
|
||||||
.pattern("#I#")
|
.pattern("#I#")
|
||||||
.define('#', ingredients.goldIngot())
|
.define('#', ingredients.goldIngot())
|
||||||
.define('C', ModRegistry.Items.COMPUTER_ADVANCED.get())
|
.define('C', ModRegistry.Items.COMPUTER_ADVANCED.get())
|
||||||
.define('I', ingredients.woodenChest())
|
.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()))))
|
.build(x -> new TransformShapedRecipe(x, List.of(new CopyComponents(ModRegistry.Items.COMPUTER_ADVANCED.get()))))
|
||||||
.save(add);
|
.save(output);
|
||||||
|
|
||||||
ShapedSpecBuilder
|
customShaped(RecipeCategory.REDSTONE, ModRegistry.Items.TURTLE_ADVANCED.get())
|
||||||
.shaped(RecipeCategory.REDSTONE, ModRegistry.Items.TURTLE_ADVANCED.get())
|
|
||||||
.pattern("###")
|
.pattern("###")
|
||||||
.pattern("#C#")
|
.pattern("#C#")
|
||||||
.pattern(" B ")
|
.pattern(" B ")
|
||||||
.define('#', ingredients.goldIngot())
|
.define('#', ingredients.goldIngot())
|
||||||
.define('C', ModRegistry.Items.TURTLE_NORMAL.get())
|
.define('C', ModRegistry.Items.TURTLE_NORMAL.get())
|
||||||
.define('B', ingredients.goldBlock())
|
.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()))))
|
.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("###")
|
||||||
.pattern("#R#")
|
.pattern("#R#")
|
||||||
.pattern("#R#")
|
.pattern("#R#")
|
||||||
.define('#', Items.STONE)
|
.define('#', Items.STONE)
|
||||||
.define('R', ingredients.redstone())
|
.define('R', ingredients.redstone())
|
||||||
.unlockedBy("has_computer", inventoryChange(COMPUTER))
|
.unlockedBy("has_computer", has(COMPUTER))
|
||||||
.save(add);
|
.save(output);
|
||||||
|
|
||||||
ShapedRecipeBuilder
|
shaped(RecipeCategory.REDSTONE, ModRegistry.Items.MONITOR_NORMAL.get())
|
||||||
.shaped(RecipeCategory.REDSTONE, ModRegistry.Items.MONITOR_NORMAL.get())
|
|
||||||
.pattern("###")
|
.pattern("###")
|
||||||
.pattern("#G#")
|
.pattern("#G#")
|
||||||
.pattern("###")
|
.pattern("###")
|
||||||
.define('#', Items.STONE)
|
.define('#', Items.STONE)
|
||||||
.define('G', ingredients.glassPane())
|
.define('G', ingredients.glassPane())
|
||||||
.unlockedBy("has_computer", inventoryChange(COMPUTER))
|
.unlockedBy("has_computer", has(COMPUTER))
|
||||||
.save(add);
|
.save(output);
|
||||||
|
|
||||||
ShapedRecipeBuilder
|
shaped(RecipeCategory.REDSTONE, ModRegistry.Items.MONITOR_ADVANCED.get(), 4)
|
||||||
.shaped(RecipeCategory.REDSTONE, ModRegistry.Items.MONITOR_ADVANCED.get(), 4)
|
|
||||||
.pattern("###")
|
.pattern("###")
|
||||||
.pattern("#G#")
|
.pattern("#G#")
|
||||||
.pattern("###")
|
.pattern("###")
|
||||||
.define('#', ingredients.goldIngot())
|
.define('#', ingredients.goldIngot())
|
||||||
.define('G', ingredients.glassPane())
|
.define('G', ingredients.glassPane())
|
||||||
.unlockedBy("has_computer", inventoryChange(COMPUTER))
|
.unlockedBy("has_computer", has(COMPUTER))
|
||||||
.save(add);
|
.save(output);
|
||||||
|
|
||||||
ShapedRecipeBuilder
|
shaped(RecipeCategory.REDSTONE, ModRegistry.Items.POCKET_COMPUTER_NORMAL.get())
|
||||||
.shaped(RecipeCategory.REDSTONE, ModRegistry.Items.POCKET_COMPUTER_NORMAL.get())
|
|
||||||
.pattern("###")
|
.pattern("###")
|
||||||
.pattern("#A#")
|
.pattern("#A#")
|
||||||
.pattern("#G#")
|
.pattern("#G#")
|
||||||
.define('#', Items.STONE)
|
.define('#', Items.STONE)
|
||||||
.define('A', Items.GOLDEN_APPLE)
|
.define('A', Items.GOLDEN_APPLE)
|
||||||
.define('G', ingredients.glassPane())
|
.define('G', ingredients.glassPane())
|
||||||
.unlockedBy("has_computer", inventoryChange(COMPUTER))
|
.unlockedBy("has_computer", has(COMPUTER))
|
||||||
.unlockedBy("has_apple", inventoryChange(Items.GOLDEN_APPLE))
|
.unlockedBy("has_apple", has(Items.GOLDEN_APPLE))
|
||||||
.save(add);
|
.save(output);
|
||||||
|
|
||||||
ShapedRecipeBuilder
|
shaped(RecipeCategory.REDSTONE, ModRegistry.Items.POCKET_COMPUTER_ADVANCED.get())
|
||||||
.shaped(RecipeCategory.REDSTONE, ModRegistry.Items.POCKET_COMPUTER_ADVANCED.get())
|
|
||||||
.pattern("###")
|
.pattern("###")
|
||||||
.pattern("#A#")
|
.pattern("#A#")
|
||||||
.pattern("#G#")
|
.pattern("#G#")
|
||||||
.define('#', ingredients.goldIngot())
|
.define('#', ingredients.goldIngot())
|
||||||
.define('A', Items.GOLDEN_APPLE)
|
.define('A', Items.GOLDEN_APPLE)
|
||||||
.define('G', ingredients.glassPane())
|
.define('G', ingredients.glassPane())
|
||||||
.unlockedBy("has_computer", inventoryChange(COMPUTER))
|
.unlockedBy("has_computer", has(COMPUTER))
|
||||||
.unlockedBy("has_apple", inventoryChange(Items.GOLDEN_APPLE))
|
.unlockedBy("has_apple", has(Items.GOLDEN_APPLE))
|
||||||
.save(add);
|
.save(output);
|
||||||
|
|
||||||
ShapedSpecBuilder
|
customShaped(RecipeCategory.REDSTONE, ModRegistry.Items.POCKET_COMPUTER_ADVANCED.get())
|
||||||
.shaped(RecipeCategory.REDSTONE, ModRegistry.Items.POCKET_COMPUTER_ADVANCED.get())
|
|
||||||
.pattern("###")
|
.pattern("###")
|
||||||
.pattern("#C#")
|
.pattern("#C#")
|
||||||
.pattern("# #")
|
.pattern("# #")
|
||||||
.define('#', ingredients.goldIngot())
|
.define('#', ingredients.goldIngot())
|
||||||
.define('C', ModRegistry.Items.POCKET_COMPUTER_NORMAL.get())
|
.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()))))
|
.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("###")
|
||||||
.pattern("#R#")
|
.pattern("#R#")
|
||||||
.pattern("#D#")
|
.pattern("#D#")
|
||||||
.define('#', Items.STONE)
|
.define('#', Items.STONE)
|
||||||
.define('R', ingredients.redstone())
|
.define('R', ingredients.redstone())
|
||||||
.define('D', ingredients.dye())
|
.define('D', ingredients.dye())
|
||||||
.unlockedBy("has_computer", inventoryChange(COMPUTER))
|
.unlockedBy("has_computer", has(COMPUTER))
|
||||||
.save(add);
|
.save(output);
|
||||||
|
|
||||||
ShapedRecipeBuilder
|
shaped(RecipeCategory.REDSTONE, ModRegistry.Items.SPEAKER.get())
|
||||||
.shaped(RecipeCategory.REDSTONE, ModRegistry.Items.SPEAKER.get())
|
|
||||||
.pattern("###")
|
.pattern("###")
|
||||||
.pattern("#N#")
|
.pattern("#N#")
|
||||||
.pattern("#R#")
|
.pattern("#R#")
|
||||||
.define('#', Items.STONE)
|
.define('#', Items.STONE)
|
||||||
.define('N', Items.NOTE_BLOCK)
|
.define('N', Items.NOTE_BLOCK)
|
||||||
.define('R', ingredients.redstone())
|
.define('R', ingredients.redstone())
|
||||||
.unlockedBy("has_computer", inventoryChange(COMPUTER))
|
.unlockedBy("has_computer", has(COMPUTER))
|
||||||
.save(add);
|
.save(output);
|
||||||
|
|
||||||
ShapedRecipeBuilder
|
shaped(RecipeCategory.REDSTONE, ModRegistry.Items.WIRED_MODEM.get())
|
||||||
.shaped(RecipeCategory.REDSTONE, ModRegistry.Items.WIRED_MODEM.get())
|
|
||||||
.pattern("###")
|
.pattern("###")
|
||||||
.pattern("#R#")
|
.pattern("#R#")
|
||||||
.pattern("###")
|
.pattern("###")
|
||||||
.define('#', Items.STONE)
|
.define('#', Items.STONE)
|
||||||
.define('R', ingredients.redstone())
|
.define('R', ingredients.redstone())
|
||||||
.unlockedBy("has_computer", inventoryChange(COMPUTER))
|
.unlockedBy("has_computer", has(COMPUTER))
|
||||||
.unlockedBy("has_cable", inventoryChange(ModRegistry.Items.CABLE.get()))
|
.unlockedBy("has_cable", has(ModRegistry.Items.CABLE.get()))
|
||||||
.save(add);
|
.save(output);
|
||||||
|
|
||||||
ShapelessRecipeBuilder
|
oneToOneConversionRecipe(ModRegistry.Items.WIRED_MODEM.get(), ModRegistry.Items.WIRED_MODEM_FULL.get(), null);
|
||||||
.shapeless(RecipeCategory.REDSTONE, ModRegistry.Items.WIRED_MODEM_FULL.get())
|
oneToOneConversionRecipe(ModRegistry.Items.WIRED_MODEM_FULL.get(), ModRegistry.Items.WIRED_MODEM.get(), null);
|
||||||
.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"));
|
|
||||||
|
|
||||||
ShapedRecipeBuilder
|
shaped(RecipeCategory.REDSTONE, ModRegistry.Items.WIRELESS_MODEM_NORMAL.get())
|
||||||
.shaped(RecipeCategory.REDSTONE, ModRegistry.Items.WIRELESS_MODEM_NORMAL.get())
|
|
||||||
.pattern("###")
|
.pattern("###")
|
||||||
.pattern("#E#")
|
.pattern("#E#")
|
||||||
.pattern("###")
|
.pattern("###")
|
||||||
.define('#', Items.STONE)
|
.define('#', Items.STONE)
|
||||||
.define('E', ingredients.enderPearl())
|
.define('E', ingredients.enderPearl())
|
||||||
.unlockedBy("has_computer", inventoryChange(COMPUTER))
|
.unlockedBy("has_computer", has(COMPUTER))
|
||||||
.save(add);
|
.save(output);
|
||||||
|
|
||||||
ShapedRecipeBuilder
|
shaped(RecipeCategory.REDSTONE, ModRegistry.Items.WIRELESS_MODEM_ADVANCED.get())
|
||||||
.shaped(RecipeCategory.REDSTONE, ModRegistry.Items.WIRELESS_MODEM_ADVANCED.get())
|
|
||||||
.pattern("###")
|
.pattern("###")
|
||||||
.pattern("#E#")
|
.pattern("#E#")
|
||||||
.pattern("###")
|
.pattern("###")
|
||||||
.define('#', ingredients.goldIngot())
|
.define('#', ingredients.goldIngot())
|
||||||
.define('E', Items.ENDER_EYE)
|
.define('E', Items.ENDER_EYE)
|
||||||
.unlockedBy("has_computer", inventoryChange(COMPUTER))
|
.unlockedBy("has_computer", has(COMPUTER))
|
||||||
.unlockedBy("has_wireless", inventoryChange(ModRegistry.Items.WIRELESS_MODEM_NORMAL.get()))
|
.unlockedBy("has_wireless", has(ModRegistry.Items.WIRELESS_MODEM_NORMAL.get()))
|
||||||
.save(add);
|
.save(output);
|
||||||
|
|
||||||
ShapelessSpecBuilder
|
customShapeless(RecipeCategory.DECORATIONS, playerHead("Cloudhunter", "6d074736-b1e9-4378-a99b-bd8777821c9c"))
|
||||||
.shapeless(RecipeCategory.DECORATIONS, playerHead("Cloudhunter", "6d074736-b1e9-4378-a99b-bd8777821c9c"))
|
|
||||||
.requires(ItemTags.SKULLS)
|
.requires(ItemTags.SKULLS)
|
||||||
.requires(ModRegistry.Items.MONITOR_NORMAL.get())
|
.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()
|
.build()
|
||||||
.save(add, ResourceLocation.fromNamespaceAndPath(ComputerCraftAPI.MOD_ID, "skull_cloudy"));
|
.save(output, ResourceLocation.fromNamespaceAndPath(ComputerCraftAPI.MOD_ID, "skull_cloudy"));
|
||||||
|
|
||||||
ShapelessSpecBuilder
|
customShapeless(RecipeCategory.DECORATIONS, playerHead("dan200", "f3c8d69b-0776-4512-8434-d1b2165909eb"))
|
||||||
.shapeless(RecipeCategory.DECORATIONS, playerHead("dan200", "f3c8d69b-0776-4512-8434-d1b2165909eb"))
|
|
||||||
.requires(ItemTags.SKULLS)
|
.requires(ItemTags.SKULLS)
|
||||||
.requires(ModRegistry.Items.COMPUTER_ADVANCED.get())
|
.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()
|
.build()
|
||||||
.save(add, ResourceLocation.fromNamespaceAndPath(ComputerCraftAPI.MOD_ID, "skull_dan200"));
|
.save(output, ResourceLocation.fromNamespaceAndPath(ComputerCraftAPI.MOD_ID, "skull_dan200"));
|
||||||
|
|
||||||
var pages = Ingredient.of(
|
var pages = Ingredient.of(
|
||||||
ModRegistry.Items.PRINTED_PAGE.get(),
|
ModRegistry.Items.PRINTED_PAGE.get(),
|
||||||
@ -477,76 +413,84 @@ final class RecipeProvider extends net.minecraft.data.recipes.RecipeProvider {
|
|||||||
Items.PAPER
|
Items.PAPER
|
||||||
);
|
);
|
||||||
|
|
||||||
ShapelessSpecBuilder
|
customShapeless(RecipeCategory.REDSTONE, ModRegistry.Items.PRINTED_PAGES.get())
|
||||||
.shapeless(RecipeCategory.REDSTONE, ModRegistry.Items.PRINTED_PAGES.get())
|
|
||||||
.requires(ingredients.string())
|
.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))
|
.build(x -> new PrintoutRecipe(x, pages, 2))
|
||||||
.save(add);
|
.save(output);
|
||||||
|
|
||||||
ShapelessSpecBuilder
|
customShapeless(RecipeCategory.REDSTONE, ModRegistry.Items.PRINTED_BOOK.get())
|
||||||
.shapeless(RecipeCategory.REDSTONE, ModRegistry.Items.PRINTED_BOOK.get())
|
|
||||||
.requires(ingredients.leather())
|
.requires(ingredients.leather())
|
||||||
.requires(ingredients.string())
|
.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))
|
.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("SRS")
|
||||||
.pattern("RCR")
|
.pattern("RCR")
|
||||||
.pattern("SRS")
|
.pattern("SRS")
|
||||||
.define('S', Items.STONE)
|
.define('S', Items.STONE)
|
||||||
.define('R', ingredients.redstone())
|
.define('R', ingredients.redstone())
|
||||||
.define('C', ModRegistry.Blocks.CABLE.get())
|
.define('C', ModRegistry.Blocks.CABLE.get())
|
||||||
.unlockedBy("has_cable", inventoryChange(ModRegistry.Blocks.CABLE.get()))
|
.unlockedBy("has_cable", has(ModRegistry.Blocks.CABLE.get()))
|
||||||
.save(add);
|
.save(output);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static DyeColor ofColour(Colour colour) {
|
private static Criterion<InventoryChangeTrigger.TriggerInstance> has(ItemLike... items) {
|
||||||
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) {
|
|
||||||
return InventoryChangeTrigger.TriggerInstance.hasItems(items);
|
return InventoryChangeTrigger.TriggerInstance.hasItems(items);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static ItemPredicate itemPredicate(ItemLike item) {
|
private ItemPredicate itemPredicate(ItemLike item) {
|
||||||
return ItemPredicate.Builder.item().of(item).build();
|
return ItemPredicate.Builder.item().of(items, item).build();
|
||||||
}
|
}
|
||||||
|
|
||||||
private static ItemPredicate itemPredicate(TagKey<Item> item) {
|
private ItemPredicate itemPredicate(TagKey<Item> item) {
|
||||||
return ItemPredicate.Builder.item().of(item).build();
|
return ItemPredicate.Builder.item().of(items, 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 static ItemStack playerHead(String name, String uuid) {
|
private static ItemStack playerHead(String name, String uuid) {
|
||||||
return DataComponentUtil.createStack(Items.PLAYER_HEAD, DataComponents.PROFILE, new ResolvableProfile(new GameProfile(UUID.fromString(uuid), name)));
|
return DataComponentUtil.createStack(Items.PLAYER_HEAD, DataComponents.PROFILE, new ResolvableProfile(new GameProfile(UUID.fromString(uuid), name)));
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void addSpecial(RecipeOutput add, Recipe<?> recipe) {
|
private ShapedSpecBuilder customShaped(RecipeCategory category, ItemStack result) {
|
||||||
add.accept(RegistryHelper.getKeyOrThrow(BuiltInRegistries.RECIPE_SERIALIZER, recipe.getSerializer()), recipe, null);
|
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
|
// 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.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.ModRegistry;
|
||||||
import dan200.computercraft.shared.computer.blocks.ComputerBlock;
|
import dan200.computercraft.shared.computer.blocks.ComputerBlock;
|
||||||
import dan200.computercraft.shared.peripheral.diskdrive.DiskDriveBlock;
|
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.MonitorBlock;
|
||||||
import dan200.computercraft.shared.peripheral.monitor.MonitorEdgeState;
|
import dan200.computercraft.shared.peripheral.monitor.MonitorEdgeState;
|
||||||
import dan200.computercraft.shared.peripheral.printer.PrinterBlock;
|
import dan200.computercraft.shared.peripheral.printer.PrinterBlock;
|
||||||
|
import dan200.computercraft.shared.turtle.TurtleOverlay;
|
||||||
import dan200.computercraft.shared.turtle.blocks.TurtleBlock;
|
import dan200.computercraft.shared.turtle.blocks.TurtleBlock;
|
||||||
import dan200.computercraft.shared.util.DirectionUtil;
|
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.core.Direction;
|
||||||
import net.minecraft.data.models.BlockModelGenerators;
|
import net.minecraft.core.component.DataComponents;
|
||||||
import net.minecraft.data.models.blockstates.*;
|
|
||||||
import net.minecraft.data.models.model.*;
|
|
||||||
import net.minecraft.resources.ResourceLocation;
|
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.Blocks;
|
||||||
import net.minecraft.world.level.block.state.properties.BlockStateProperties;
|
import net.minecraft.world.level.block.state.properties.BlockStateProperties;
|
||||||
import net.minecraft.world.level.block.state.properties.BooleanProperty;
|
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.BiFunction;
|
||||||
import java.util.function.Function;
|
import java.util.function.Function;
|
||||||
|
|
||||||
import static net.minecraft.data.models.model.ModelLocationUtils.getModelLocation;
|
import static net.minecraft.client.data.models.model.ModelLocationUtils.getModelLocation;
|
||||||
import static net.minecraft.data.models.model.TextureMapping.getBlockTexture;
|
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 CURSOR = TextureSlot.create("cursor");
|
||||||
private static final TextureSlot LEFT = TextureSlot.create("left");
|
private static final TextureSlot LEFT = TextureSlot.create("left");
|
||||||
private static final TextureSlot RIGHT = TextureSlot.create("right");
|
private static final TextureSlot RIGHT = TextureSlot.create("right");
|
||||||
@ -92,6 +101,8 @@ class BlockModelProvider {
|
|||||||
registerMonitor(generators, ModRegistry.Blocks.MONITOR_ADVANCED.get());
|
registerMonitor(generators, ModRegistry.Blocks.MONITOR_ADVANCED.get());
|
||||||
|
|
||||||
generators.createHorizontallyRotatedBlock(ModRegistry.Blocks.SPEAKER.get(), TexturedModel.ORIENTABLE_ONLY_TOP);
|
generators.createHorizontallyRotatedBlock(ModRegistry.Blocks.SPEAKER.get(), TexturedModel.ORIENTABLE_ONLY_TOP);
|
||||||
|
registerSimpleItemModel(generators, ModRegistry.Blocks.SPEAKER.get());
|
||||||
|
|
||||||
registerDiskDrive(generators);
|
registerDiskDrive(generators);
|
||||||
registerPrinter(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_normal", "block/wireless_modem_normal_face");
|
||||||
registerTurtleModem(generators, "block/turtle_modem_advanced", "block/wireless_modem_advanced_face");
|
registerTurtleModem(generators, "block/turtle_modem_advanced", "block/wireless_modem_advanced_face");
|
||||||
|
|
||||||
generators.blockStateOutput.accept(MultiVariantGenerator.multiVariant(
|
generators.blockStateOutput.accept(
|
||||||
ModRegistry.Blocks.LECTERN.get(),
|
BlockModelGenerators.createSimpleBlock(ModRegistry.Blocks.LECTERN.get(), getModelLocation(Blocks.LECTERN))
|
||||||
Variant.variant().with(VariantProperties.MODEL, ModelLocationUtils.getModelLocation(Blocks.LECTERN))
|
.with(createHorizontalFacingDispatch())
|
||||||
).with(createHorizontalFacingDispatch()));
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void registerDiskDrive(BlockModelGenerators generators) {
|
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) {
|
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) {
|
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) {
|
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()
|
var model = TURTLE.create(block, new TextureMapping()
|
||||||
.put(TextureSlot.FRONT, getBlockTexture(block, "_front"))
|
.put(TextureSlot.FRONT, getBlockTexture(block, "_front"))
|
||||||
.put(TextureSlot.BACK, getBlockTexture(block, "_back"))
|
.put(TextureSlot.BACK, getBlockTexture(block, "_back"))
|
||||||
@ -188,17 +206,21 @@ class BlockModelProvider {
|
|||||||
.put(BACKPACK, getBlockTexture(block, "_backpack")),
|
.put(BACKPACK, getBlockTexture(block, "_backpack")),
|
||||||
generators.modelOutput
|
generators.modelOutput
|
||||||
);
|
);
|
||||||
generators.blockStateOutput.accept(
|
|
||||||
MultiVariantGenerator.multiVariant(block, Variant.variant().with(VariantProperties.MODEL, model))
|
|
||||||
.with(createHorizontalFacingDispatch())
|
|
||||||
);
|
|
||||||
|
|
||||||
generators.modelOutput.accept(getModelLocation(block.asItem()), () -> {
|
generators.itemModelOutput.accept(block.asItem(), ItemModelUtils.composite(
|
||||||
var out = new JsonObject();
|
ItemModelUtils.conditional(
|
||||||
out.addProperty("loader", "computercraft:turtle");
|
new HasComponent(DataComponents.DYED_COLOR, false),
|
||||||
out.addProperty("model", model.toString());
|
ItemModelUtils.plainModel(TurtleBlockEntityRenderer.COLOUR_TURTLE_MODEL),
|
||||||
return out;
|
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) {
|
private static void registerWirelessModem(BlockModelGenerators generators, WirelessModemBlock block) {
|
||||||
@ -207,7 +229,7 @@ class BlockModelProvider {
|
|||||||
.with(createModelDispatch(WirelessModemBlock.ON,
|
.with(createModelDispatch(WirelessModemBlock.ON,
|
||||||
on -> modemModel(generators, getModelLocation(block, on ? "_on" : "_off"), getBlockTexture(block, "_face" + (on ? "_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) {
|
private static void registerWiredModems(BlockModelGenerators generators) {
|
||||||
@ -227,8 +249,8 @@ class BlockModelProvider {
|
|||||||
);
|
);
|
||||||
})));
|
})));
|
||||||
|
|
||||||
generators.delegateItemModel(fullBlock, getModelLocation(fullBlock, "_off"));
|
generators.registerSimpleItemModel(fullBlock, getModelLocation(fullBlock, "_off"));
|
||||||
generators.delegateItemModel(ModRegistry.Items.WIRED_MODEM.get(), ResourceLocation.fromNamespaceAndPath(ComputerCraftAPI.MOD_ID, "block/wired_modem_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) {
|
private static ResourceLocation modemModel(BlockModelGenerators generators, ResourceLocation name, ResourceLocation texture) {
|
||||||
@ -264,7 +286,7 @@ class BlockModelProvider {
|
|||||||
.with(createVerticalFacingDispatch(MonitorBlock.ORIENTATION))
|
.with(createVerticalFacingDispatch(MonitorBlock.ORIENTATION))
|
||||||
.with(createModelDispatch(MonitorBlock.STATE, edge -> getModelLocation(block, edge == MonitorEdgeState.NONE ? "" : "_" + edge.getSerializedName())))
|
.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) {
|
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.blockStateOutput.accept(generator);
|
||||||
|
generators.registerSimpleItemModel(ModRegistry.Items.CABLE.get(), getModelLocation(ModRegistry.Items.CABLE.get()));
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void registerRedstoneControl(BlockModelGenerators generators) {
|
private static void registerRedstoneControl(BlockModelGenerators generators) {
|
||||||
var redstoneControl = ModRegistry.Blocks.REDSTONE_RELAY.get();
|
var redstoneControl = ModRegistry.Blocks.REDSTONE_RELAY.get();
|
||||||
var model = ModelTemplates.CUBE_ORIENTABLE_TOP_BOTTOM.create(
|
generators.createHorizontallyRotatedBlock(redstoneControl, TexturedModel.ORIENTABLE);
|
||||||
redstoneControl, TextureMapping.orientableCube(redstoneControl), generators.modelOutput
|
registerSimpleItemModel(generators, redstoneControl);
|
||||||
);
|
|
||||||
generators.blockStateOutput.accept(
|
|
||||||
MultiVariantGenerator.multiVariant(redstoneControl, Variant.variant().with(VariantProperties.MODEL, model))
|
|
||||||
.with(createHorizontalFacingDispatch())
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -396,6 +414,10 @@ class BlockModelProvider {
|
|||||||
registerTurtleUpgrade(generators, name + "_on", texture + "_on");
|
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) {
|
private static VariantProperties.Rotation toXAngle(Direction direction) {
|
||||||
return switch (direction) {
|
return switch (direction) {
|
||||||
default -> VariantProperties.Rotation.R0;
|
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.AdvancementRewards;
|
||||||
import net.minecraft.advancements.Criterion;
|
import net.minecraft.advancements.Criterion;
|
||||||
import net.minecraft.advancements.critereon.RecipeUnlockedTrigger;
|
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.RecipeBuilder;
|
||||||
import net.minecraft.data.recipes.RecipeCategory;
|
import net.minecraft.data.recipes.RecipeCategory;
|
||||||
import net.minecraft.data.recipes.RecipeOutput;
|
import net.minecraft.data.recipes.RecipeOutput;
|
||||||
|
import net.minecraft.resources.ResourceKey;
|
||||||
import net.minecraft.resources.ResourceLocation;
|
import net.minecraft.resources.ResourceLocation;
|
||||||
import net.minecraft.world.item.Item;
|
import net.minecraft.world.item.Item;
|
||||||
import net.minecraft.world.item.ItemStack;
|
import net.minecraft.world.item.ItemStack;
|
||||||
@ -30,12 +33,14 @@ import java.util.function.Function;
|
|||||||
* @see ShapelessSpecBuilder
|
* @see ShapelessSpecBuilder
|
||||||
*/
|
*/
|
||||||
public abstract class AbstractRecipeBuilder<S extends AbstractRecipeBuilder<S, O>, O> {
|
public abstract class AbstractRecipeBuilder<S extends AbstractRecipeBuilder<S, O>, O> {
|
||||||
|
protected final HolderGetter<Item> items;
|
||||||
private final RecipeCategory category;
|
private final RecipeCategory category;
|
||||||
protected final ItemStack result;
|
protected final ItemStack result;
|
||||||
private String group = "";
|
private String group = "";
|
||||||
private final Map<String, Criterion<?>> criteria = new LinkedHashMap<>();
|
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.category = category;
|
||||||
this.result = result;
|
this.result = result;
|
||||||
}
|
}
|
||||||
@ -112,17 +117,23 @@ public abstract class AbstractRecipeBuilder<S extends AbstractRecipeBuilder<S, O
|
|||||||
|
|
||||||
public void save(RecipeOutput output, ResourceLocation id) {
|
public void save(RecipeOutput output, ResourceLocation id) {
|
||||||
if (criteria.isEmpty()) throw new IllegalStateException("No way of obtaining recipe " + id);
|
if (criteria.isEmpty()) throw new IllegalStateException("No way of obtaining recipe " + id);
|
||||||
|
|
||||||
|
var key = recipeKey(id);
|
||||||
var advancement = output.advancement()
|
var advancement = output.advancement()
|
||||||
.addCriterion("has_the_recipe", RecipeUnlockedTrigger.unlocked(id))
|
.addCriterion("has_the_recipe", RecipeUnlockedTrigger.unlocked(key))
|
||||||
.rewards(AdvancementRewards.Builder.recipe(id))
|
.rewards(AdvancementRewards.Builder.recipe(key))
|
||||||
.requirements(AdvancementRequirements.Strategy.OR);
|
.requirements(AdvancementRequirements.Strategy.OR);
|
||||||
for (var entry : criteria.entrySet()) advancement.addCriterion(entry.getKey(), entry.getValue());
|
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) {
|
public void save(RecipeOutput output) {
|
||||||
save(output, RecipeBuilder.getDefaultRecipeId(result));
|
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.RecipeProperties;
|
||||||
import dan200.computercraft.shared.recipe.ShapedRecipeSpec;
|
import dan200.computercraft.shared.recipe.ShapedRecipeSpec;
|
||||||
|
import net.minecraft.core.HolderGetter;
|
||||||
import net.minecraft.data.recipes.RecipeCategory;
|
import net.minecraft.data.recipes.RecipeCategory;
|
||||||
import net.minecraft.data.recipes.ShapedRecipeBuilder;
|
import net.minecraft.data.recipes.ShapedRecipeBuilder;
|
||||||
import net.minecraft.tags.TagKey;
|
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 List<String> rows = new ArrayList<>();
|
||||||
private final Map<Character, Ingredient> key = new LinkedHashMap<>();
|
private final Map<Character, Ingredient> key = new LinkedHashMap<>();
|
||||||
|
|
||||||
private ShapedSpecBuilder(RecipeCategory category, ItemStack result) {
|
public ShapedSpecBuilder(HolderGetter<Item> items, RecipeCategory category, ItemStack result) {
|
||||||
super(category, result);
|
super(items, 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 define(char key, Ingredient ingredient) {
|
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) {
|
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) {
|
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.RecipeProperties;
|
||||||
import dan200.computercraft.shared.recipe.ShapelessRecipeSpec;
|
import dan200.computercraft.shared.recipe.ShapelessRecipeSpec;
|
||||||
|
import net.minecraft.core.HolderGetter;
|
||||||
import net.minecraft.core.NonNullList;
|
import net.minecraft.core.NonNullList;
|
||||||
import net.minecraft.data.recipes.RecipeCategory;
|
import net.minecraft.data.recipes.RecipeCategory;
|
||||||
import net.minecraft.data.recipes.ShapelessRecipeBuilder;
|
import net.minecraft.data.recipes.ShapelessRecipeBuilder;
|
||||||
@ -21,20 +22,12 @@ import net.minecraft.world.level.ItemLike;
|
|||||||
public final class ShapelessSpecBuilder extends AbstractRecipeBuilder<ShapelessSpecBuilder, ShapelessRecipeSpec> {
|
public final class ShapelessSpecBuilder extends AbstractRecipeBuilder<ShapelessSpecBuilder, ShapelessRecipeSpec> {
|
||||||
private final NonNullList<Ingredient> ingredients = NonNullList.create();
|
private final NonNullList<Ingredient> ingredients = NonNullList.create();
|
||||||
|
|
||||||
private ShapelessSpecBuilder(RecipeCategory category, ItemStack result) {
|
public ShapelessSpecBuilder(HolderGetter<Item> items, RecipeCategory category, ItemStack result) {
|
||||||
super(category, result);
|
super(items, 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 requires(Ingredient ingredient, int count) {
|
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;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -43,15 +36,15 @@ public final class ShapelessSpecBuilder extends AbstractRecipeBuilder<ShapelessS
|
|||||||
}
|
}
|
||||||
|
|
||||||
public ShapelessSpecBuilder requires(ItemLike item) {
|
public ShapelessSpecBuilder requires(ItemLike item) {
|
||||||
return requires(Ingredient.of(new ItemStack(item)));
|
return requires(Ingredient.of(item));
|
||||||
}
|
}
|
||||||
|
|
||||||
public ShapelessSpecBuilder requires(ItemLike item, int count) {
|
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) {
|
public ShapelessSpecBuilder requires(TagKey<Item> item) {
|
||||||
return requires(Ingredient.of(item));
|
return requires(Ingredient.of(items.getOrThrow(item)));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"variants": {
|
"variants": {
|
||||||
"facing=east": {"model": "computercraft:block/redstone_relay", "y": 90},
|
"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=south": {"model": "computercraft:block/redstone_relay", "y": 180},
|
||||||
"facing=west": {"model": "computercraft:block/redstone_relay", "y": 270}
|
"facing=west": {"model": "computercraft:block/redstone_relay", "y": 270}
|
||||||
}
|
}
|
||||||
|
@ -1,8 +1 @@
|
|||||||
{
|
{"variants": {"": {"model": "computercraft:block/turtle_advanced_particle"}}}
|
||||||
"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}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
@ -1,8 +1 @@
|
|||||||
{
|
{"variants": {"": {"model": "computercraft:block/turtle_normal_particle"}}}
|
||||||
"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}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
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
Loading…
x
Reference in New Issue
Block a user