fix: Accidental hard dependency on sodium in mixins

This commit is contained in:
Linnea Gräf
2025-04-12 14:49:41 +02:00
parent a3b3ec6490
commit c3bf4a82a2
11 changed files with 256 additions and 109 deletions

View File

@@ -8,7 +8,6 @@
import com.google.common.hash.Hashing import com.google.common.hash.Hashing
import com.google.devtools.ksp.gradle.KspAATask import com.google.devtools.ksp.gradle.KspAATask
import com.google.devtools.ksp.gradle.KspTaskJvm
import com.google.gson.Gson import com.google.gson.Gson
import com.google.gson.JsonObject import com.google.gson.JsonObject
import moe.nea.licenseextractificator.LicenseDiscoveryTask import moe.nea.licenseextractificator.LicenseDiscoveryTask
@@ -16,7 +15,6 @@ import moe.nea.mcautotranslations.gradle.CollectTranslations
import net.fabricmc.loom.LoomGradleExtension import net.fabricmc.loom.LoomGradleExtension
import org.apache.tools.ant.taskdefs.condition.Os import org.apache.tools.ant.taskdefs.condition.Os
import org.jetbrains.kotlin.gradle.dsl.JvmTarget import org.jetbrains.kotlin.gradle.dsl.JvmTarget
import org.jetbrains.kotlin.gradle.plugin.SubpluginOption
import org.jetbrains.kotlin.gradle.tasks.KotlinCompile import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
import java.nio.charset.StandardCharsets import java.nio.charset.StandardCharsets
import java.util.Base64 import java.util.Base64
@@ -166,14 +164,16 @@ fun createIsolatedSourceSet(name: String, path: String = "compat/$name", isEnabl
extendsFrom(getByName(mainSS.annotationProcessorConfigurationName)) extendsFrom(getByName(mainSS.annotationProcessorConfigurationName))
} }
(mainSS.runtimeOnlyConfigurationName) { (mainSS.runtimeOnlyConfigurationName) {
extendsFrom(getByName(ss.runtimeClasspathConfigurationName)) if (isEnabled)
extendsFrom(getByName(ss.runtimeClasspathConfigurationName))
} }
("ksp$upperName") { ("ksp$upperName") {
extendsFrom(ksp.get()) extendsFrom(ksp.get())
} }
} }
dependencies { dependencies {
runtimeOnly(ss.output) if (isEnabled)
runtimeOnly(ss.output)
(ss.implementationConfigurationName)(project.files(tasks.compileKotlin.map { it.destinationDirectory })) (ss.implementationConfigurationName)(project.files(tasks.compileKotlin.map { it.destinationDirectory }))
(ss.implementationConfigurationName)(project.files(tasks.compileJava.map { it.destinationDirectory })) (ss.implementationConfigurationName)(project.files(tasks.compileJava.map { it.destinationDirectory }))
} }
@@ -337,6 +337,7 @@ loom {
File(it.output.classesDirs.asPath).absolutePath File(it.output.classesDirs.asPath).absolutePath
}) })
property("mixin.debug.export", "true") property("mixin.debug.export", "true")
property("mixin.debug", "true")
parseEnvFile(file(".env")).forEach { (t, u) -> parseEnvFile(file(".env")).forEach { (t, u) ->
environmentVariable(t, u) environmentVariable(t, u)

View File

@@ -15,11 +15,13 @@ import java.util.stream.Collectors;
public class MixinPlugin implements IMixinConfigPlugin { public class MixinPlugin implements IMixinConfigPlugin {
AutoDiscoveryPlugin autoDiscoveryPlugin = new AutoDiscoveryPlugin(); AutoDiscoveryPlugin autoDiscoveryPlugin = new AutoDiscoveryPlugin();
public static String mixinPackage; public static List<MixinPlugin> instances = new ArrayList<>();
public String mixinPackage;
@Override @Override
public void onLoad(String mixinPackage) { public void onLoad(String mixinPackage) {
MixinExtrasBootstrap.init(); MixinExtrasBootstrap.init();
MixinPlugin.mixinPackage = mixinPackage; instances.add(this);
this.mixinPackage = mixinPackage;
autoDiscoveryPlugin.setMixinPackage(mixinPackage); autoDiscoveryPlugin.setMixinPackage(mixinPackage);
} }
@@ -52,7 +54,7 @@ public class MixinPlugin implements IMixinConfigPlugin {
} }
public static List<String> appliedMixins = new ArrayList<>(); public List<String> appliedMixins = new ArrayList<>();
@Override @Override
public void postApply(String targetClassName, ClassNode targetClass, String mixinClassName, IMixinInfo mixinInfo) { public void postApply(String targetClassName, ClassNode targetClass, String mixinClassName, IMixinInfo mixinInfo) {

View File

@@ -5,6 +5,7 @@ import net.fabricmc.loader.api.FabricLoader;
import net.minecraft.block.BlockState; import net.minecraft.block.BlockState;
import net.minecraft.client.render.block.BlockRenderManager; import net.minecraft.client.render.block.BlockRenderManager;
import net.minecraft.client.render.chunk.SectionBuilder; import net.minecraft.client.render.chunk.SectionBuilder;
import net.minecraft.client.render.model.BlockStateModel;
import net.minecraft.util.math.BlockPos; import net.minecraft.util.math.BlockPos;
import org.objectweb.asm.Opcodes; import org.objectweb.asm.Opcodes;
import org.objectweb.asm.Type; import org.objectweb.asm.Type;
@@ -18,100 +19,100 @@ import org.objectweb.asm.tree.VarInsnNode;
public class SectionBuilderRiser extends RiserUtils { public class SectionBuilderRiser extends RiserUtils {
@IntermediaryName(SectionBuilder.class) @IntermediaryName(SectionBuilder.class)
String SectionBuilder; String SectionBuilder;
@IntermediaryName(BlockPos.class) @IntermediaryName(BlockPos.class)
String BlockPos; String BlockPos;
@IntermediaryName(BlockRenderManager.class) @IntermediaryName(BlockRenderManager.class)
String BlockRenderManager; String BlockRenderManager;
@IntermediaryName(BlockState.class) @IntermediaryName(BlockState.class)
String BlockState; String BlockState;
// @IntermediaryName(BakedModel.class) @IntermediaryName(BlockStateModel.class)
// String BakedModel; String BlockStateModel;
String CustomBlockTextures = "moe.nea.firmament.features.texturepack.CustomBlockTextures"; String CustomBlockTextures = "moe.nea.firmament.features.texturepack.CustomBlockTextures";
Type getModelDesc = Type.getMethodType( Type getModelDesc = Type.getMethodType(
getTypeForClassName(BlockRenderManager), getTypeForClassName(BlockRenderManager),
getTypeForClassName(BlockState) getTypeForClassName(BlockState)
); );
String getModel = remapper.mapMethodName( String getModel = remapper.mapMethodName(
"intermediary", "intermediary",
Intermediary.<BlockRenderManager>className(), Intermediary.<BlockRenderManager>className(),
Intermediary.methodName(net.minecraft.client.render.block.BlockRenderManager::getModel), Intermediary.methodName(net.minecraft.client.render.block.BlockRenderManager::getModel),
Type.getMethodDescriptor( Type.getMethodDescriptor(
// TODO: fix this riser getTypeForClassName(Intermediary.<BlockStateModel>className()),
// getTypeForClassName(Intermediary.<BakedModel>className()), getTypeForClassName(Intermediary.<BlockState>className())
getTypeForClassName(Intermediary.<BlockState>className()) )
) );
);
@Override @Override
public void addTinkerers() { public void addTinkerers() {
if (FabricLoader.getInstance().isModLoaded("fabric-renderer-indigo")) if (FabricLoader.getInstance().isModLoaded("fabric-renderer-indigo"))
ClassTinkerers.addTransformation(SectionBuilder, this::handle, true); ClassTinkerers.addTransformation(SectionBuilder, this::handle, true);
} }
private void handle(ClassNode classNode) { private void handle(ClassNode classNode) {
for (MethodNode method : classNode.methods) { System.out.println("AVAST! "+ getModel);
if ((method.name.endsWith("$fabric-renderer-indigo$hookBuildRenderBlock") for (MethodNode method : classNode.methods) {
|| method.name.endsWith("$fabric-renderer-indigo$hookChunkBuildTessellate")) && if ((method.name.endsWith("$fabric-renderer-indigo$hookBuildRenderBlock")
method.name.startsWith("redirect$")) { || method.name.endsWith("$fabric-renderer-indigo$hookChunkBuildTessellate")) &&
handleIndigo(method); method.name.startsWith("redirect$")) {
return; handleIndigo(method);
} return;
} }
System.err.println("Could not inject indigo rendering hook. Is a custom renderer installed (e.g. sodium)?"); }
} System.err.println("Could not inject indigo rendering hook. Is a custom renderer installed (e.g. sodium)?");
}
private void handleIndigo(MethodNode method) { private void handleIndigo(MethodNode method) {
LocalVariableNode blockPosVar = null, blockStateVar = null; LocalVariableNode blockPosVar = null, blockStateVar = null;
for (LocalVariableNode localVariable : method.localVariables) { for (LocalVariableNode localVariable : method.localVariables) {
if (Type.getType(localVariable.desc).equals(getTypeForClassName(BlockPos))) { if (Type.getType(localVariable.desc).equals(getTypeForClassName(BlockPos))) {
blockPosVar = localVariable; blockPosVar = localVariable;
} }
if (Type.getType(localVariable.desc).equals(getTypeForClassName(BlockState))) { if (Type.getType(localVariable.desc).equals(getTypeForClassName(BlockState))) {
blockStateVar = localVariable; blockStateVar = localVariable;
} }
} }
if (blockPosVar == null || blockStateVar == null) { if (blockPosVar == null || blockStateVar == null) {
System.err.println("Firmament could inject into indigo: missing either block pos or blockstate"); System.err.println("Firmament could inject into indigo: missing either block pos or blockstate");
return; return;
} }
for (AbstractInsnNode instruction : method.instructions) { for (AbstractInsnNode instruction : method.instructions) {
if (instruction.getOpcode() != Opcodes.INVOKEVIRTUAL) continue; if (instruction.getOpcode() != Opcodes.INVOKEVIRTUAL) continue;
var methodInsn = (MethodInsnNode) instruction; var methodInsn = (MethodInsnNode) instruction;
if (!(methodInsn.name.equals(getModel) && Type.getObjectType(methodInsn.owner).equals(getTypeForClassName(BlockRenderManager)))) if (!(methodInsn.name.equals(getModel) && Type.getObjectType(methodInsn.owner).equals(getTypeForClassName(BlockRenderManager))))
continue; continue;
method.instructions.insertBefore( method.instructions.insertBefore(
methodInsn, methodInsn,
new MethodInsnNode( new MethodInsnNode(
Opcodes.INVOKESTATIC, Opcodes.INVOKESTATIC,
getTypeForClassName(CustomBlockTextures).getInternalName(), getTypeForClassName(CustomBlockTextures).getInternalName(),
"enterFallbackCall", "enterFallbackCall",
Type.getMethodDescriptor(Type.VOID_TYPE) Type.getMethodDescriptor(Type.VOID_TYPE)
)); ));
var insnList = new InsnList(); var insnList = new InsnList();
insnList.add(new MethodInsnNode( insnList.add(new MethodInsnNode(
Opcodes.INVOKESTATIC, Opcodes.INVOKESTATIC,
getTypeForClassName(CustomBlockTextures).getInternalName(), getTypeForClassName(CustomBlockTextures).getInternalName(),
"exitFallbackCall", "exitFallbackCall",
Type.getMethodDescriptor(Type.VOID_TYPE) Type.getMethodDescriptor(Type.VOID_TYPE)
)); ));
insnList.add(new VarInsnNode(Opcodes.ALOAD, blockPosVar.index)); insnList.add(new VarInsnNode(Opcodes.ALOAD, blockPosVar.index));
insnList.add(new VarInsnNode(Opcodes.ALOAD, blockStateVar.index)); insnList.add(new VarInsnNode(Opcodes.ALOAD, blockStateVar.index));
insnList.add(new MethodInsnNode( insnList.add(new MethodInsnNode(
Opcodes.INVOKESTATIC, Opcodes.INVOKESTATIC,
getTypeForClassName(CustomBlockTextures).getInternalName(), getTypeForClassName(CustomBlockTextures).getInternalName(),
"patchIndigo", "patchIndigo",
Type.getMethodDescriptor( Type.getMethodDescriptor(
// getTypeForClassName(BakedModel), getTypeForClassName(BlockStateModel),
// getTypeForClassName(BakedModel), getTypeForClassName(BlockStateModel),
getTypeForClassName(BlockPos), getTypeForClassName(BlockPos),
getTypeForClassName(BlockState)), getTypeForClassName(BlockState)),
false false
)); ));
method.instructions.insert(methodInsn, insnList); method.instructions.insert(methodInsn, insnList);
} }
} }
} }

View File

@@ -26,6 +26,7 @@ import net.fabricmc.loader.api.Version
import net.fabricmc.loader.api.metadata.ModMetadata import net.fabricmc.loader.api.metadata.ModMetadata
import org.apache.logging.log4j.LogManager import org.apache.logging.log4j.LogManager
import org.apache.logging.log4j.Logger import org.apache.logging.log4j.Logger
import org.spongepowered.asm.launch.MixinBootstrap
import kotlinx.coroutines.CoroutineName import kotlinx.coroutines.CoroutineName
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Job import kotlinx.coroutines.Job

View File

@@ -262,7 +262,8 @@ fun firmamentCommand() = literal("firmament") {
source.sendFeedback(Text.stringifiedTranslatable("firmament.sbinfo.gametype", locrawInfo.gametype)) source.sendFeedback(Text.stringifiedTranslatable("firmament.sbinfo.gametype", locrawInfo.gametype))
source.sendFeedback(Text.stringifiedTranslatable("firmament.sbinfo.mode", locrawInfo.mode)) source.sendFeedback(Text.stringifiedTranslatable("firmament.sbinfo.mode", locrawInfo.mode))
source.sendFeedback(Text.stringifiedTranslatable("firmament.sbinfo.map", locrawInfo.map)) source.sendFeedback(Text.stringifiedTranslatable("firmament.sbinfo.map", locrawInfo.map))
source.sendFeedback(tr("firmament.sbinfo.custommining", "Custom Mining: ${formatBool(locrawInfo.skyblockLocation?.hasCustomMining ?: false)}")) source.sendFeedback(tr("firmament.sbinfo.custommining",
"Custom Mining: ${formatBool(locrawInfo.skyblockLocation?.hasCustomMining ?: false)}"))
} }
} }
} }
@@ -313,13 +314,15 @@ fun firmamentCommand() = literal("firmament") {
} }
thenLiteral("mixins") { thenLiteral("mixins") {
thenExecute { thenExecute {
source.sendFeedback(Text.translatable("firmament.mixins.start")) MixinPlugin.instances.forEach { plugin ->
MixinPlugin.appliedMixins source.sendFeedback(tr("firmament.mixins.start.package", "Mixins (base ${plugin.mixinPackage}):"))
.map { it.removePrefix(MixinPlugin.mixinPackage) } plugin.appliedMixins
.forEach { .map { it.removePrefix(plugin.mixinPackage) }
source.sendFeedback(Text.literal(" - ").withColor(0xD020F0) .forEach {
.append(Text.literal(it).withColor(0xF6BA20))) source.sendFeedback(Text.literal(" - ").withColor(0xD020F0)
} .append(Text.literal(it).withColor(0xF6BA20)))
}
}
} }
} }
thenLiteral("repo") { thenLiteral("repo") {

View File

@@ -3,6 +3,10 @@ package moe.nea.firmament.features.debug
import java.io.File import java.io.File
import java.nio.file.Path import java.nio.file.Path
import java.util.concurrent.CompletableFuture import java.util.concurrent.CompletableFuture
import org.objectweb.asm.ClassReader
import org.objectweb.asm.Type
import org.objectweb.asm.tree.ClassNode
import org.spongepowered.asm.mixin.Mixin
import kotlinx.serialization.json.encodeToStream import kotlinx.serialization.json.encodeToStream
import kotlin.io.path.absolute import kotlin.io.path.absolute
import kotlin.io.path.exists import kotlin.io.path.exists
@@ -10,11 +14,14 @@ import net.minecraft.client.MinecraftClient
import net.minecraft.text.Text import net.minecraft.text.Text
import moe.nea.firmament.Firmament import moe.nea.firmament.Firmament
import moe.nea.firmament.annotations.Subscribe import moe.nea.firmament.annotations.Subscribe
import moe.nea.firmament.events.DebugInstantiateEvent
import moe.nea.firmament.events.TickEvent import moe.nea.firmament.events.TickEvent
import moe.nea.firmament.features.FirmamentFeature import moe.nea.firmament.features.FirmamentFeature
import moe.nea.firmament.gui.config.ManagedConfig import moe.nea.firmament.gui.config.ManagedConfig
import moe.nea.firmament.init.MixinPlugin
import moe.nea.firmament.util.MC import moe.nea.firmament.util.MC
import moe.nea.firmament.util.TimeMark import moe.nea.firmament.util.TimeMark
import moe.nea.firmament.util.asm.AsmAnnotationUtil
import moe.nea.firmament.util.iterate import moe.nea.firmament.util.iterate
object DeveloperFeatures : FirmamentFeature { object DeveloperFeatures : FirmamentFeature {
@@ -41,6 +48,42 @@ object DeveloperFeatures : FirmamentFeature {
this.missingTranslations = missingTranslations this.missingTranslations = missingTranslations
} }
@Subscribe
fun loadAllMixinClasses(event: DebugInstantiateEvent) {
val allMixinClasses = mutableSetOf<String>()
MixinPlugin.instances.forEach { plugin ->
val prefix = plugin.mixinPackage + "."
val classes = plugin.mixins.map { prefix + it }
allMixinClasses.addAll(classes)
for (cls in classes) {
val targets = javaClass.classLoader.getResourceAsStream("${cls.replace(".", "/")}.class").use {
val node = ClassNode()
ClassReader(it).accept(node, 0)
val mixins = mutableListOf<Mixin>()
(node.visibleAnnotations.orEmpty() + node.invisibleAnnotations.orEmpty()).forEach {
val annotationType = Type.getType(it.desc)
val mixinType = Type.getType(Mixin::class.java)
if (mixinType == annotationType) {
mixins.add(AsmAnnotationUtil.createProxy(Mixin::class.java, it))
}
}
mixins.flatMap { it.targets.toList() } + mixins.flatMap { it.value.map { it.java.name } }
}
for (target in targets)
try {
Firmament.logger.debug("Loading ${target} to force instantiate ${cls}")
Class.forName(target, true, javaClass.classLoader)
} catch (ex: Throwable) {
Firmament.logger.error("Could not load class ${target} that has been mixind by $cls", ex)
}
}
}
Firmament.logger.info("Forceloaded all Firmament mixins:")
val applied = MixinPlugin.instances.flatMap { it.appliedMixins }.toSet()
applied.forEach { Firmament.logger.info(" - ${it}") }
require(allMixinClasses == applied)
}
@Subscribe @Subscribe
fun dumpMissingTranslations(tickEvent: TickEvent) { fun dumpMissingTranslations(tickEvent: TickEvent) {
val toDump = missingTranslations ?: return val toDump = missingTranslations ?: return

View File

@@ -0,0 +1,89 @@
package moe.nea.firmament.util.asm
import com.google.common.base.Defaults
import java.lang.reflect.InvocationHandler
import java.lang.reflect.Method
import java.lang.reflect.Proxy
import org.objectweb.asm.Type
import org.objectweb.asm.tree.AnnotationNode
object AsmAnnotationUtil {
class AnnotationProxy(
val originalType: Class<out Annotation>,
val annotationNode: AnnotationNode,
) : InvocationHandler {
val offsets = annotationNode.values.withIndex()
.chunked(2)
.map { it.first() }
.associate { (idx, value) -> value as String to idx + 1 }
fun nestArrayType(depth: Int, comp: Class<*>): Class<*> =
if (depth == 0) comp
else java.lang.reflect.Array.newInstance(nestArrayType(depth - 1, comp), 0).javaClass
fun unmap(
value: Any?,
comp: Class<*>,
depth: Int,
): Any? {
value ?: return null
if (depth > 0)
return ((value as List<Any>)
.map { unmap(it, comp, depth - 1) } as java.util.List<Any>)
.toArray(java.lang.reflect.Array.newInstance(nestArrayType(depth - 1, comp), 0) as Array<*>)
if (comp.isEnum) {
comp as Class<out Enum<*>>
when (value) {
is String -> return java.lang.Enum.valueOf(comp, value)
is List<*> -> return java.lang.Enum.valueOf(comp, value[1] as String)
else -> error("Unknown enum variant $value for $comp")
}
}
when (value) {
is Type -> return Class.forName(value.className)
is AnnotationNode -> return createProxy(comp as Class<out Annotation>, value)
is String, is Boolean, is Byte, is Double, is Int, is Float, is Long, is Short, is Char -> return value
}
error("Unknown enum variant $value for $comp")
}
fun defaultFor(fullType: Class<*>): Any? {
if (fullType.isArray) return java.lang.reflect.Array.newInstance(fullType.componentType, 0)
if (fullType.isPrimitive) {
return Defaults.defaultValue(fullType)
}
if (fullType == String::class.java)
return ""
return null
}
override fun invoke(
proxy: Any,
method: Method,
args: Array<out Any?>?
): Any? {
val name = method.name
val ret = method.returnType
val retU = generateSequence(ret) { if (it.isArray) it.componentType else null }
.toList()
val arrayDepth = retU.size - 1
val componentType = retU.last()
val off = offsets[name]
if (off == null) {
return defaultFor(ret)
}
return unmap(annotationNode.values[off], componentType, arrayDepth)
}
}
fun <T : Annotation> createProxy(
annotationClass: Class<T>,
annotationNode: AnnotationNode
): T {
require(Type.getType(annotationClass) == Type.getType(annotationNode.desc))
return Proxy.newProxyInstance(javaClass.classLoader,
arrayOf(annotationClass),
AnnotationProxy(annotationClass, annotationNode)) as T
}
}

View File

@@ -316,6 +316,15 @@ object CustomBlockTextures {
) )
} }
/**
* Used by [moe.nea.firmament.init.SectionBuilderRiser]
*/
@JvmStatic
fun patchIndigo(original: BlockStateModel, pos: BlockPos?, state: BlockState): BlockStateModel {
return getReplacementModel(state, pos) ?: original
}
@JvmStatic @JvmStatic
fun collectExtraModels(modelsCollector: ReferencedModelsCollector) { fun collectExtraModels(modelsCollector: ReferencedModelsCollector) {
preparationFuture.join().collectAllReplacements() preparationFuture.join().collectAllReplacements()

View File

@@ -26,7 +26,6 @@ public class PatchLegacyTexturePathsIntoArmorLayers {
// legacy format: "assets/{identifier.namespace}/textures/models/armor/{identifier.path}_layer_{isLegs ? 2 : 1}{suffix}.png" // legacy format: "assets/{identifier.namespace}/textures/models/armor/{identifier.path}_layer_{isLegs ? 2 : 1}{suffix}.png"
// suffix is sadly not available to us here. this means leather armor will look a bit shite // suffix is sadly not available to us here. this means leather armor will look a bit shite
var legacyIdentifier = this.textureId.withPath((textureName) -> { var legacyIdentifier = this.textureId.withPath((textureName) -> {
String var10000 = layerType.asString();
return "textures/models/armor/" + textureName + "_layer_" + return "textures/models/armor/" + textureName + "_layer_" +
(layerType == EquipmentModel.LayerType.HUMANOID_LEGGINGS ? 2 : 1) (layerType == EquipmentModel.LayerType.HUMANOID_LEGGINGS ? 2 : 1)
+ ".png"; + ".png";

View File

@@ -26,7 +26,7 @@ public class ReplaceItemModelPatch implements IntrospectableItemModelManager {
private Function<Identifier, ItemModel> modelGetter; private Function<Identifier, ItemModel> modelGetter;
@WrapOperation( @WrapOperation(
method = "update(Lnet/minecraft/client/render/item/ItemRenderState;Lnet/minecraft/item/ItemStack;Lnet/minecraft/item/ModelTransformationMode;Lnet/minecraft/world/World;Lnet/minecraft/entity/LivingEntity;I)V", method = "update",
at = @At(value = "INVOKE", target = "Lnet/minecraft/item/ItemStack;get(Lnet/minecraft/component/ComponentType;)Ljava/lang/Object;")) at = @At(value = "INVOKE", target = "Lnet/minecraft/item/ItemStack;get(Lnet/minecraft/component/ComponentType;)Ljava/lang/Object;"))
private Object replaceItemModelByIdentifier(ItemStack instance, ComponentType componentType, Operation<Object> original) { private Object replaceItemModelByIdentifier(ItemStack instance, ComponentType componentType, Operation<Object> original) {
var override = CustomItemModelEvent.getModelIdentifier(instance, this); var override = CustomItemModelEvent.getModelIdentifier(instance, this);

View File

@@ -301,7 +301,6 @@
"firmament.inventory-buttons.save-preset": "Save Preset", "firmament.inventory-buttons.save-preset": "Save Preset",
"firmament.key.category": "Firmament", "firmament.key.category": "Firmament",
"firmament.keybinding.external": "%s", "firmament.keybinding.external": "%s",
"firmament.mixins.start": "Applied firmament mixins:",
"firmament.modapi.event": "Received mod API event: %s", "firmament.modapi.event": "Received mod API event: %s",
"firmament.poweruser.entity.armor": "Entity Armor:", "firmament.poweruser.entity.armor": "Entity Armor:",
"firmament.poweruser.entity.armor.item": " - %s", "firmament.poweruser.entity.armor.item": " - %s",