WIP: Port to compilation on 1.21.4

This commit is contained in:
Linnea Gräf
2024-12-07 13:26:03 +01:00
parent cdb5e60f52
commit bf7795df22
77 changed files with 780 additions and 1446 deletions

View File

@@ -25,7 +25,7 @@ plugins {
alias(libs.plugins.kotlin.plugin.ksp)
// alias(libs.plugins.loom)
// TODO: use arch loom once they update to 1.8
id("fabric-loom") version "1.8.9"
id("fabric-loom") version "1.9.2"
alias(libs.plugins.shadow)
id("moe.nea.licenseextractificator")
id("moe.nea.mc-auto-translations") version "0.1.0"
@@ -129,13 +129,8 @@ val collectTranslations by tasks.registering(CollectTranslations::class) {
val compatSourceSets: MutableSet<SourceSet> = mutableSetOf()
fun createIsolatedSourceSet(name: String, path: String = "compat/$name", isEnabled: Boolean = true): SourceSet {
val ss = sourceSets.create(name) {
if (isEnabled) {
this.java.setSrcDirs(listOf(layout.projectDirectory.dir("src/$path/java")))
this.kotlin.setSrcDirs(listOf(layout.projectDirectory.dir("src/$path/java")))
} else {
this.java.setSrcDirs(listOf<File>())
this.kotlin.setSrcDirs(listOf<File>())
}
this.java.setSrcDirs(listOf(layout.projectDirectory.dir("src/$path/java")))
this.kotlin.setSrcDirs(listOf(layout.projectDirectory.dir("src/$path/java")))
}
val mainSS = sourceSets.main.get()
val upperName = ss.name.capitalizeN()
@@ -221,15 +216,16 @@ val testAgent by configurations.creating {
val configuredSourceSet = createIsolatedSourceSet("configured",
isEnabled = false) // Wait for update (also low prio, because configured sucks)
val sodiumSourceSet = createIsolatedSourceSet("sodium")
val sodiumSourceSet = createIsolatedSourceSet("sodium", isEnabled = false)
val citResewnSourceSet = createIsolatedSourceSet("citresewn", isEnabled = false) // TODO: Wait for update
val yaclSourceSet = createIsolatedSourceSet("yacl")
val yaclSourceSet = createIsolatedSourceSet("yacl", isEnabled = false)
val explosiveEnhancementSourceSet =
createIsolatedSourceSet("explosiveEnhancement", isEnabled = false) // TODO: wait for their port
val wildfireGenderSourceSet = createIsolatedSourceSet("wildfireGender", isEnabled = false) // TODO: wait on their port
val modmenuSourceSet = createIsolatedSourceSet("modmenu")
val reiSourceSet = createIsolatedSourceSet("rei")
val moulconfigSourceSet = createIsolatedSourceSet("moulconfig")
val modmenuSourceSet = createIsolatedSourceSet("modmenu", isEnabled = false)
val reiSourceSet = createIsolatedSourceSet("rei", isEnabled = false)
val moulconfigSourceSet = createIsolatedSourceSet("moulconfig", isEnabled = false)
val customTexturesSourceSet = createIsolatedSourceSet("texturePacks", "texturePacks")
dependencies {
// Minecraft dependencies

View File

@@ -3,7 +3,7 @@
// SPDX-License-Identifier: CC0-1.0
plugins {
kotlin("jvm") version "1.8.10"
kotlin("jvm") version "2.1.0"
`kotlin-dsl`
}
repositories {

View File

@@ -3,21 +3,23 @@
# SPDX-License-Identifier: CC0-1.0
[versions]
minecraft = "1.21.3"
minecraft = "1.21.4"
# Update from https://kotlinlang.org/
kotlin = "2.0.21"
kotlin = "2.1.0"
# Update from https://github.com/google/ksp/releases
kotlin_ksp = "2.0.21-1.0.26"
kotlin_ksp = "2.1.0-1.0.29"
# Update from https://linkie.shedaniel.me/dependencies?loader=fabric
fabric_loader = "0.16.9"
fabric_api = "0.107.0+1.21.3"
fabric_kotlin = "1.12.3+kotlin.2.0.21"
yarn = "1.21.3+build.2"
fabric_api = "0.110.5+1.21.4"
yarn = "1.21.4+build.1"
modmenu = "13.0.0-beta.1"
architectury = "15.0.1"
rei = "17.0.789"
modmenu = "12.0.0-beta.1"
architectury = "14.0.4"
# Update from https://maven.fabricmc.net/net/fabricmc/fabric-language-kotlin/
fabric_kotlin = "1.13.0+kotlin.2.1.0"
# Update from https://maven.architectury.dev/dev/architectury/loom/dev.architectury.loom.gradle.plugin/
loom = "1.7.414"
@@ -26,16 +28,16 @@ loom = "1.7.414"
qolify = "1.6.0-1.21.1"
# Update from https://modrinth.com/mod/sodium/versions?l=fabric
sodium = "mc1.21.3-0.6.0-beta.4-fabric"
sodium = "mc1.21.4-0.6.2-fabric"
# Update from https://modrinth.com/mod/freecam/versions?l=fabric
freecammod = "vomskVK3"
freecammod = "1.3.1+mc1.21.3"
# Update from https://modrinth.com/mod/no-chat-reports/versions?l=fabric
ncr = "Fabric-1.21.3-v2.10.0"
# Update from https://modrinth.com/mod/female-gender/versions?l=fabric
femalegender = "kJmjQvAS"
femalegender = "4.3+1.21.4"
# Update from https://modrinth.com/mod/explosive-enhancement/versions?l=fabric
explosiveenhancement = "1.2.3-1.21.0"
@@ -49,12 +51,13 @@ citresewn = "1.2.0+1.21"
devauth = "1.2.0"
# Update from https://ktor.io/
ktor = "3.0.1"
ktor = "3.0.2"
# Update from https://repo.nea.moe/#/releases/moe/nea/neurepoparser
neurepoparser = "1.6.0"
# Update from https://github.com/HotswapProjects/HotswapAgent/releases
# TODO: bump to 2.0.1
hotswap_agent = "1.4.2-SNAPSHOT"
# Update from https://github.com/LlamaLad7/MixinExtras/tags

View File

@@ -4,6 +4,6 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-8.10.2-bin.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-8.11.1-bin.zip
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists

View File

@@ -59,6 +59,14 @@ public class InitReplacer extends TreeScanner<Void, Void> {
var target = plugin.utils.getAnnotationValue(jcAnnotation, "value");
var targetClass = plugin.utils.resolveClassLiteralExpression(target).tsym.flatName().toString();
var intermediaryClass = mappingTree.resolveClassToIntermediary(targetClass);
if (intermediaryClass == null){
plugin.utils.reportError(
compilationUnitTree.getSourceFile(),
jcNode.init,
"Unknown class name " + targetClass
);
return super.visitVariable(node, unused);
}
var remapper = treeMaker.Select(treeMaker.This(classTree.type), names.fromString("remapper"));
var remappingCall = treeMaker.Apply(
List.nil(),

View File

@@ -57,7 +57,12 @@ public class IntermediaryMethodReplacer extends TreeScanner<Void, Void> {
}
var head = node.typeargs.head;
var resolved = plugin.utils.resolveClassName(head, compilationUnit);
var mappedName = mappings.resolveClassToIntermediary(resolved.tsym.flatName().toString());
var sourceName = resolved.tsym.flatName().toString();
var mappedName = mappings.resolveClassToIntermediary(sourceName);
if (mappedName == null) {
plugin.utils.reportError(sourceFile, node, "Unknown class name " + sourceName);
return;
}
fieldAccess.name = plugin.names.fromString("id");
node.typeargs = List.nil();
node.args = List.of(plugin.treeMaker.Literal(mappedName));

View File

@@ -9,40 +9,43 @@ import java.util.stream.Collectors;
public class MappingTree {
private final Map<String, TinyClass> classLookup;
private final int targetIndex;
private final int sourceIndex;
private final Map<String, TinyClass> classLookup;
private final int targetIndex;
private final int sourceIndex;
public MappingTree(TinyFile tinyV2File, String sourceNamespace, String targetNamespace) {
sourceIndex = tinyV2File.getHeader().getNamespaces().indexOf(sourceNamespace);
if (sourceIndex < 0)
throw new RuntimeException("Could not find source namespace " + sourceNamespace + " in mappings file.");
this.classLookup = tinyV2File
.getClassEntries()
.stream()
.collect(Collectors.toMap(it -> it.getClassNames().get(sourceIndex), it -> it));
targetIndex = tinyV2File.getHeader().getNamespaces().indexOf(targetNamespace);
if (targetIndex < 0)
throw new RuntimeException("Could not find target namespace " + targetNamespace + " in mappings file.");
}
public MappingTree(TinyFile tinyV2File, String sourceNamespace, String targetNamespace) {
sourceIndex = tinyV2File.getHeader().getNamespaces().indexOf(sourceNamespace);
if (sourceIndex < 0)
throw new RuntimeException("Could not find source namespace " + sourceNamespace + " in mappings file.");
this.classLookup = tinyV2File
.getClassEntries()
.stream()
.collect(Collectors.toMap(it -> it.getClassNames().get(sourceIndex), it -> it));
targetIndex = tinyV2File.getHeader().getNamespaces().indexOf(targetNamespace);
if (targetIndex < 0)
throw new RuntimeException("Could not find target namespace " + targetNamespace + " in mappings file.");
}
public String resolveMethodToIntermediary(String className, String methodName) {
var classData = classLookup.get(className.replace(".", "/"));
TinyMethod candidate = null;
for (TinyMethod method : classData.getMethods()) {
if (method.getMethodNames().get(sourceIndex).equals(methodName)) {
if (candidate != null) {
throw new RuntimeException("Found two candidates for method " + className + "." + methodName);
}
candidate = method;
}
}
return candidate.getMethodNames().get(targetIndex);
}
public String resolveMethodToIntermediary(String className, String methodName) {
var classData = classLookup.get(className.replace(".", "/"));
TinyMethod candidate = null;
for (TinyMethod method : classData.getMethods()) {
if (method.getMethodNames().get(sourceIndex).equals(methodName)) {
if (candidate != null) {
throw new RuntimeException("Found two candidates for method " + className + "." + methodName);
}
candidate = method;
}
}
return candidate.getMethodNames().get(targetIndex);
}
public String resolveClassToIntermediary(String className) {
return classLookup.get(className.replace(".", "/"))
.getClassNames().get(targetIndex)
.replace("/", ".");
}
public String resolveClassToIntermediary(String className) {
var cls = classLookup.get(className.replace(".", "/"));
if (cls == null) {
return null;
}
return cls.getClassNames().get(targetIndex)
.replace("/", ".");
}
}

View File

@@ -111,7 +111,7 @@ public class Utils {
var error = diagnostics.error(
JCDiagnostic.DiagnosticFlag.API,
log.currentSource(),
((JCTree) node).pos(),
node == null ? null : ((JCTree) node).pos(),
"firmament.generic",
message
);

View File

@@ -7,6 +7,6 @@ public class EarlyRiser implements Runnable {
new ClientPlayerRiser().addTinkerers();
new HandledScreenRiser().addTinkerers();
new SectionBuilderRiser().addTinkerers();
new ItemColorsSodiumRiser().addTinkerers();
// TODO: new ItemColorsSodiumRiser().addTinkerers();
}
}

View File

@@ -3,7 +3,6 @@ package moe.nea.firmament.init;
import me.shedaniel.mm.api.ClassTinkerers;
import net.minecraft.client.gui.Element;
import net.minecraft.client.gui.ParentElement;
import org.objectweb.asm.Opcodes;
import org.objectweb.asm.Type;
import org.objectweb.asm.tree.ClassNode;

View File

@@ -1,64 +0,0 @@
package moe.nea.firmament.init;
import me.shedaniel.mm.api.ClassTinkerers;
import moe.nea.firmament.util.ErrorUtil;
import net.fabricmc.loader.api.FabricLoader;
import net.minecraft.client.color.item.ItemColorProvider;
import net.minecraft.client.color.item.ItemColors;
import net.minecraft.item.ItemStack;
import org.objectweb.asm.Opcodes;
import org.objectweb.asm.Type;
import org.objectweb.asm.tree.ClassNode;
import org.objectweb.asm.tree.InsnList;
import org.objectweb.asm.tree.InsnNode;
import org.objectweb.asm.tree.MethodInsnNode;
import org.objectweb.asm.tree.VarInsnNode;
public class ItemColorsSodiumRiser extends RiserUtils {
@IntermediaryName(ItemColors.class)
String ItemColors;
@IntermediaryName(ItemColorProvider.class)
String ItemColorProvider;
@IntermediaryName(ItemStack.class)
String ItemStack;
String getColorProvider = "sodium$getColorProvider";
Type getColorProviderDesc = Type.getMethodType(getTypeForClassName(ItemColorProvider),
getTypeForClassName(ItemStack));
@Override
public void addTinkerers() {
ClassTinkerers.addTransformation(ItemColors, this::addSodiumOverride, true);
}
private void addSodiumOverride(ClassNode classNode) {
var node = findMethod(classNode, getColorProvider, getColorProviderDesc);
if (node == null) {
if (!FabricLoader.getInstance().isModLoaded("sodium"))
ErrorUtil.INSTANCE.softError("Sodium is present, but sodium color override could not be injected.");
return;
}
var p = node.instructions.getFirst();
while (p != null) {
if (p.getOpcode() == Opcodes.ARETURN) {
node.instructions.insertBefore(
p,
mkOverrideSodiumCall()
);
}
p = p.getNext();
}
}
private InsnList mkOverrideSodiumCall() {
var insnList = new InsnList();
insnList.add(new VarInsnNode(Opcodes.ALOAD, 0));
insnList.add(new InsnNode(Opcodes.SWAP));
insnList.add(new MethodInsnNode(Opcodes.INVOKEVIRTUAL,
getTypeForClassName(ItemColors).getInternalName(),
"overrideSodium_firmament",
Type.getMethodType(getTypeForClassName(ItemColorProvider),
getTypeForClassName(ItemColorProvider)).getDescriptor(),
false));
return insnList;
}
}

View File

@@ -1,36 +0,0 @@
package moe.nea.firmament.mixins;
import moe.nea.firmament.events.CustomItemModelEvent;
import moe.nea.firmament.features.texturepack.CustomGlobalTextures;
import net.minecraft.client.render.item.ItemModels;
import net.minecraft.client.render.model.BakedModel;
import net.minecraft.client.render.model.BakedModelManager;
import net.minecraft.item.Item;
import net.minecraft.item.ItemStack;
import net.minecraft.util.Identifier;
import org.spongepowered.asm.mixin.Final;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Shadow;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
import java.util.Map;
@Mixin(ItemModels.class)
public class CustomModelEventPatch {
@Inject(method = "getModel(Lnet/minecraft/item/ItemStack;)Lnet/minecraft/client/render/model/BakedModel;", at = @At("HEAD"), cancellable = true)
public void onGetModel(ItemStack stack, CallbackInfoReturnable<BakedModel> cir) {
var $this = (ItemModels) (Object) this;
var model = CustomItemModelEvent.getModel(stack, $this);
if (model == null) {
model = CustomGlobalTextures.replaceGlobalModel($this, stack);
}
if (model != null) {
cir.setReturnValue(model);
}
}
}

View File

@@ -18,7 +18,6 @@ import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
@Mixin(ClientPlayNetworkHandler.class)
public abstract class IncomingPacketListenerPatches {
@ModifyExpressionValue(method = "onCommandTree", at = @At(value = "NEW", target = "(Lcom/mojang/brigadier/tree/RootCommandNode;)Lcom/mojang/brigadier/CommandDispatcher;", remap = false))
public CommandDispatcher onOnCommandTree(CommandDispatcher dispatcher) {
MaskCommands.Companion.publish(new MaskCommands(dispatcher));
@@ -31,7 +30,7 @@ public abstract class IncomingPacketListenerPatches {
packet.getParameters(),
new Vec3d(packet.getX(), packet.getY(), packet.getZ()),
new Vector3f(packet.getOffsetX(), packet.getOffsetY(), packet.getOffsetZ()),
packet.isLongDistance(),
packet.isImportant(),
packet.getCount(),
packet.getSpeed()
);

View File

@@ -1,35 +0,0 @@
package moe.nea.firmament.mixins.custommodels;
import com.llamalad7.mixinextras.sugar.Local;
import com.llamalad7.mixinextras.sugar.ref.LocalRef;
import moe.nea.firmament.features.texturepack.BakedModelExtra;
import net.minecraft.client.render.VertexConsumerProvider;
import net.minecraft.client.render.item.ItemRenderer;
import net.minecraft.client.render.model.BakedModel;
import net.minecraft.client.util.math.MatrixStack;
import net.minecraft.item.ItemStack;
import net.minecraft.item.ModelTransformationMode;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
@Mixin(ItemRenderer.class)
public class ApplyHeadModelInItemRenderer {
@Inject(method = "renderItem(Lnet/minecraft/item/ItemStack;Lnet/minecraft/item/ModelTransformationMode;ZLnet/minecraft/client/util/math/MatrixStack;Lnet/minecraft/client/render/VertexConsumerProvider;IILnet/minecraft/client/render/model/BakedModel;ZF)V",
at = @At("HEAD"))
private void applyHeadModel(ItemStack stack, ModelTransformationMode transformationMode, boolean leftHanded,
MatrixStack matrices, VertexConsumerProvider vertexConsumers, int light, int overlay,
BakedModel model, boolean useInventoryModel, float z, CallbackInfo ci,
@Local(argsOnly = true) LocalRef<BakedModel> modelMut
) {
var extra = BakedModelExtra.cast(model);
if (transformationMode == ModelTransformationMode.HEAD && extra != null) {
var headModel = extra.getHeadModel_firmament();
if (headModel != null) {
modelMut.set(headModel);
}
}
}
}

View File

@@ -1,42 +0,0 @@
package moe.nea.firmament.mixins.custommodels;
import moe.nea.firmament.features.texturepack.BakedModelExtra;
import moe.nea.firmament.features.texturepack.TintOverrides;
import net.minecraft.client.render.model.BakedModel;
import net.minecraft.client.render.model.BasicBakedModel;
import org.jetbrains.annotations.Nullable;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Unique;
@Mixin(BasicBakedModel.class)
public class BakedModelDataHolderBasic implements BakedModelExtra {
@Unique
private BakedModel headModel;
@Unique
@Nullable
private TintOverrides tintOverrides;
@Nullable
@Override
public BakedModel getHeadModel_firmament() {
return headModel;
}
@Override
public void setHeadModel_firmament(@Nullable BakedModel headModel) {
this.headModel = headModel;
}
@Override
public @Nullable TintOverrides getTintOverrides_firmament() {
return tintOverrides;
}
@Override
public void setTintOverrides_firmament(@Nullable TintOverrides tintOverrides) {
this.tintOverrides = tintOverrides;
}
}

View File

@@ -1,43 +0,0 @@
package moe.nea.firmament.mixins.custommodels;
import moe.nea.firmament.features.texturepack.BakedModelExtra;
import moe.nea.firmament.features.texturepack.TintOverrides;
import net.minecraft.client.render.model.BakedModel;
import net.minecraft.client.render.model.BuiltinBakedModel;
import org.jetbrains.annotations.Nullable;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Unique;
@Mixin(BuiltinBakedModel.class)
public class BakedModelDataHolderBuiltin implements BakedModelExtra {
@Unique
@Nullable
private BakedModel headModel;
@Unique
@Nullable
private TintOverrides tintOverrides;
@Override
public @Nullable TintOverrides getTintOverrides_firmament() {
return tintOverrides;
}
@Override
public void setTintOverrides_firmament(@Nullable TintOverrides tintOverrides) {
this.tintOverrides = tintOverrides;
}
@Nullable
@Override
public BakedModel getHeadModel_firmament() {
return headModel;
}
@Override
public void setHeadModel_firmament(@Nullable BakedModel headModel) {
this.headModel = headModel;
}
}

View File

@@ -1,28 +0,0 @@
package moe.nea.firmament.mixins.custommodels;
import moe.nea.firmament.features.texturepack.BakedOverrideData;
import moe.nea.firmament.features.texturepack.FirmamentModelPredicate;
import net.minecraft.client.render.model.json.ModelOverrideList;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Unique;
@Mixin(ModelOverrideList.BakedOverride.class)
public class BakedOverrideDataHolder implements BakedOverrideData {
@Unique
private FirmamentModelPredicate[] firmamentOverrides;
@Nullable
@Override
public FirmamentModelPredicate[] getFirmamentOverrides() {
return firmamentOverrides;
}
@Override
public void setFirmamentOverrides(@NotNull FirmamentModelPredicate[] overrides) {
this.firmamentOverrides = overrides;
}
}

View File

@@ -1,57 +0,0 @@
package moe.nea.firmament.mixins.custommodels;
import com.llamalad7.mixinextras.injector.wrapoperation.Operation;
import com.llamalad7.mixinextras.injector.wrapoperation.WrapOperation;
import com.llamalad7.mixinextras.sugar.Local;
import moe.nea.firmament.features.texturepack.BakedModelExtra;
import net.minecraft.block.AbstractSkullBlock;
import net.minecraft.block.Block;
import net.minecraft.block.Blocks;
import net.minecraft.client.render.VertexConsumerProvider;
import net.minecraft.client.render.entity.LivingEntityRenderer;
import net.minecraft.client.render.entity.feature.HeadFeatureRenderer;
import net.minecraft.client.render.entity.model.EntityModel;
import net.minecraft.client.render.entity.model.ModelWithHead;
import net.minecraft.client.render.entity.state.LivingEntityRenderState;
import net.minecraft.client.render.model.BakedModel;
import net.minecraft.client.util.math.MatrixStack;
import net.minecraft.entity.EquipmentSlot;
import net.minecraft.item.BlockItem;
import net.minecraft.item.ItemStack;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.injection.At;
@Mixin(HeadFeatureRenderer.class)
public class HeadModelReplacerPatch<S extends LivingEntityRenderState, M extends EntityModel<S> & ModelWithHead> {
/**
* This class serves to disable the replacing of head models with the vanilla block model. Vanilla first selects loads
* the model containing the head model regularly in {@link LivingEntityRenderer#updateRenderState}, but then discards
* the model in {@link HeadFeatureRenderer#render(MatrixStack, VertexConsumerProvider, int, LivingEntityRenderState, float, float)}
* if it detects a skull block. This serves to disable that functionality if a head model override is present.
*/
@WrapOperation(method = "render(Lnet/minecraft/client/util/math/MatrixStack;Lnet/minecraft/client/render/VertexConsumerProvider;ILnet/minecraft/client/render/entity/state/LivingEntityRenderState;FF)V",
at = @At(value = "INVOKE", target = "Lnet/minecraft/item/BlockItem;getBlock()Lnet/minecraft/block/Block;"))
private Block replaceSkull(BlockItem instance, Operation<Block> original, @Local BakedModel bakedModel) {
var oldBlock = original.call(instance);
if (oldBlock instanceof AbstractSkullBlock) {
var extra = BakedModelExtra.cast(bakedModel);
if (extra != null && extra.getHeadModel_firmament() != null)
return Blocks.ENCHANTING_TABLE; // Any non skull block. Let's choose the enchanting table because it is very distinct.
}
return oldBlock;
}
/**
* We disable the has model override, since texture packs get precedent to server data.
*/
@WrapOperation(method = "render(Lnet/minecraft/client/util/math/MatrixStack;Lnet/minecraft/client/render/VertexConsumerProvider;ILnet/minecraft/client/render/entity/state/LivingEntityRenderState;FF)V",
at = @At(value = "INVOKE", target = "Lnet/minecraft/client/render/entity/feature/ArmorFeatureRenderer;hasModel(Lnet/minecraft/item/ItemStack;Lnet/minecraft/entity/EquipmentSlot;)Z"))
private boolean replaceHasModel(ItemStack stack, EquipmentSlot slot, Operation<Boolean> original,
@Local BakedModel bakedModel) {
var extra = BakedModelExtra.cast(bakedModel);
if (extra != null && extra.getHeadModel_firmament() != null)
return false;
return original.call(stack, slot);
}
}

View File

@@ -1,39 +0,0 @@
package moe.nea.firmament.mixins.custommodels;
import moe.nea.firmament.features.texturepack.TintOverrides;
import moe.nea.firmament.init.ItemColorsSodiumRiser;
import net.minecraft.client.color.item.ItemColorProvider;
import net.minecraft.client.color.item.ItemColors;
import net.minecraft.item.ItemStack;
import org.jetbrains.annotations.Nullable;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
@Mixin(ItemColors.class)
public class ItemColorRemovalPatch {
/**
* @see ItemColorsSodiumRiser
*/
private @Nullable ItemColorProvider overrideSodium_firmament(@Nullable ItemColorProvider original) {
var tintOverrides = TintOverrides.Companion.getCurrentOverrides();
if (!tintOverrides.hasOverrides()) return original;
return (stack, tintIndex) -> {
var override = tintOverrides.getOverride(tintIndex);
if (override != null) return override;
if (original != null) return original.getColor(stack, tintIndex);
return -1;
};
}
@Inject(method = "getColor", at = @At("HEAD"), cancellable = true)
private void overrideGetColorCall(ItemStack item, int tintIndex, CallbackInfoReturnable<Integer> cir) {
var tintOverrides = TintOverrides.Companion.getCurrentOverrides();
var override = tintOverrides.getOverride(tintIndex);
if (override != null)
cir.setReturnValue(override);
}
}

View File

@@ -1,22 +0,0 @@
package moe.nea.firmament.mixins.custommodels;
import com.llamalad7.mixinextras.injector.ModifyReturnValue;
import com.llamalad7.mixinextras.sugar.Local;
import moe.nea.firmament.features.texturepack.JsonUnbakedModelFirmExtra;
import net.minecraft.client.render.model.json.ItemModelGenerator;
import net.minecraft.client.render.model.json.JsonUnbakedModel;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.injection.At;
@Mixin(ItemModelGenerator.class)
public class ItemModelGeneratorJsonUnbakedModelCopy {
@ModifyReturnValue(method = "create", at = @At("RETURN"))
private JsonUnbakedModel copyExtraModelData(JsonUnbakedModel original, @Local(argsOnly = true) JsonUnbakedModel oldModel) {
var extra = ((JsonUnbakedModelFirmExtra) original);
var oldExtra = ((JsonUnbakedModelFirmExtra) oldModel);
extra.setHeadModel_firmament(oldExtra.getHeadModel_firmament());
extra.setTintOverrides_firmament(oldExtra.getTintOverrides_firmament());
return original;
}
}

View File

@@ -1,35 +0,0 @@
package moe.nea.firmament.mixins.custommodels;
import moe.nea.firmament.features.texturepack.BakedModelExtra;
import moe.nea.firmament.features.texturepack.TintOverrides;
import net.minecraft.client.render.VertexConsumerProvider;
import net.minecraft.client.render.item.ItemRenderer;
import net.minecraft.client.render.model.BakedModel;
import net.minecraft.client.util.math.MatrixStack;
import net.minecraft.item.ItemStack;
import net.minecraft.item.ModelTransformationMode;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
@Mixin(value = ItemRenderer.class, priority = 1010)
public class ItemRendererTintContextPatch {
@Inject(method = "renderItem(Lnet/minecraft/item/ItemStack;Lnet/minecraft/item/ModelTransformationMode;ZLnet/minecraft/client/util/math/MatrixStack;Lnet/minecraft/client/render/VertexConsumerProvider;IILnet/minecraft/client/render/model/BakedModel;ZF)V",
at = @At(value = "HEAD"), allow = 1)
private void onStartRendering(ItemStack stack, ModelTransformationMode transformationMode, boolean leftHanded, MatrixStack matrices, VertexConsumerProvider vertexConsumers, int light, int overlay, BakedModel model, boolean useInventoryModel, float z, CallbackInfo ci) {
var extra = BakedModelExtra.cast(model);
if (extra != null) {
TintOverrides.Companion.enter(extra.getTintOverrides_firmament());
}
}
@Inject(method = "renderItem(Lnet/minecraft/item/ItemStack;Lnet/minecraft/item/ModelTransformationMode;ZLnet/minecraft/client/util/math/MatrixStack;Lnet/minecraft/client/render/VertexConsumerProvider;IILnet/minecraft/client/render/model/BakedModel;ZF)V",
at = @At("TAIL"), allow = 1)
private void onEndRendering(ItemStack stack, ModelTransformationMode transformationMode, boolean leftHanded, MatrixStack matrices, VertexConsumerProvider vertexConsumers, int light, int overlay, BakedModel model, boolean useInventoryModel, float z, CallbackInfo ci) {
var extra = BakedModelExtra.cast(model);
if (extra != null) {
TintOverrides.Companion.exit(extra.getTintOverrides_firmament());
}
}
}

View File

@@ -1,130 +0,0 @@
package moe.nea.firmament.mixins.custommodels;
import com.llamalad7.mixinextras.injector.ModifyReturnValue;
import com.llamalad7.mixinextras.sugar.Local;
import moe.nea.firmament.features.texturepack.BakedModelExtra;
import moe.nea.firmament.features.texturepack.JsonUnbakedModelFirmExtra;
import moe.nea.firmament.features.texturepack.TintOverrides;
import moe.nea.firmament.util.ErrorUtil;
import net.minecraft.client.render.model.BakedModel;
import net.minecraft.client.render.model.Baker;
import net.minecraft.client.render.model.ModelRotation;
import net.minecraft.client.render.model.UnbakedModel;
import net.minecraft.client.render.model.json.JsonUnbakedModel;
import net.minecraft.util.Identifier;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Shadow;
import org.spongepowered.asm.mixin.Unique;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
import java.util.Objects;
@Mixin(JsonUnbakedModel.class)
public abstract class JsonUnbakedModelDataHolder implements JsonUnbakedModelFirmExtra {
@Shadow
@Nullable
protected JsonUnbakedModel parent;
@Shadow
public abstract String toString();
@Unique
@Nullable
public Identifier headModel;
@Unique
@Nullable
public TintOverrides tintOverrides;
@Unique
@Nullable
public TintOverrides mergedTintOverrides;
@Override
public void setTintOverrides_firmament(@Nullable TintOverrides tintOverrides) {
this.tintOverrides = tintOverrides;
this.mergedTintOverrides = null;
}
@Override
public @NotNull TintOverrides getTintOverrides_firmament() {
if (mergedTintOverrides != null)
return mergedTintOverrides;
var mergedTintOverrides = parent == null ? new TintOverrides()
: ((JsonUnbakedModelFirmExtra) parent).getTintOverrides_firmament();
if (tintOverrides != null)
mergedTintOverrides = tintOverrides.mergeWithParent(mergedTintOverrides);
this.mergedTintOverrides = mergedTintOverrides;
return mergedTintOverrides;
}
@Override
public void setHeadModel_firmament(@Nullable Identifier identifier) {
this.headModel = identifier;
}
@Override
public @Nullable Identifier getHeadModel_firmament() {
if (this.headModel != null) return this.headModel;
if (this.parent == null) return null;
return ((JsonUnbakedModelFirmExtra) this.parent).getHeadModel_firmament();
}
@Inject(method = "resolve", at = @At("HEAD"))
private void addDependencies(UnbakedModel.Resolver resolver, CallbackInfo ci) {
var headModel = getHeadModel_firmament();
if (headModel != null) {
resolver.resolve(headModel);
}
}
private void addExtraBakeInfo(BakedModel bakedModel, Baker baker) {
if (!this.toString().contains("minecraft") && this.toString().contains("crimson")) {
System.out.println("Found non minecraft model " + this);
}
var extra = BakedModelExtra.cast(bakedModel);
if (extra != null) {
var headModel = getHeadModel_firmament();
if (headModel != null) {
extra.setHeadModel_firmament(baker.bake(headModel, ModelRotation.X0_Y0));
}
if (getTintOverrides_firmament().hasOverrides()) {
extra.setTintOverrides_firmament(getTintOverrides_firmament());
}
}
}
/**
* @see ProvideBakerToJsonUnbakedModelPatch
*/
@Override
public void storeExtraBaker_firmament(@NotNull Baker baker) {
this.storedBaker = baker;
}
@Unique
private Baker storedBaker;
@ModifyReturnValue(
method = "bake(Ljava/util/function/Function;Lnet/minecraft/client/render/model/ModelBakeSettings;Z)Lnet/minecraft/client/render/model/BakedModel;",
at = @At("RETURN"))
private BakedModel bakeExtraInfoWithoutBaker(BakedModel original) {
if (storedBaker != null) {
addExtraBakeInfo(original, storedBaker);
storedBaker = null;
}
return original;
}
@ModifyReturnValue(
method = {
"bake(Lnet/minecraft/client/render/model/Baker;Ljava/util/function/Function;Lnet/minecraft/client/render/model/ModelBakeSettings;)Lnet/minecraft/client/render/model/BakedModel;"
},
at = @At(value = "RETURN"))
private BakedModel bakeExtraInfo(BakedModel original, @Local(argsOnly = true) Baker baker) {
addExtraBakeInfo(original, baker);
return original;
}
}

View File

@@ -1,28 +0,0 @@
package moe.nea.firmament.mixins.custommodels;
import moe.nea.firmament.features.texturepack.FirmamentModelPredicate;
import moe.nea.firmament.features.texturepack.ModelOverrideData;
import net.minecraft.client.render.model.json.ModelOverride;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Unique;
@Mixin(ModelOverride.class)
public class ModelOverrideDataHolder implements ModelOverrideData {
@Unique
private FirmamentModelPredicate[] overrides;
@Nullable
@Override
public FirmamentModelPredicate[] getFirmamentOverrides() {
return overrides;
}
@Override
public void setFirmamentOverrides(@NotNull FirmamentModelPredicate[] overrides) {
this.overrides = overrides;
}
}

View File

@@ -1,31 +0,0 @@
package moe.nea.firmament.mixins.custommodels;
import com.google.gson.JsonObject;
import com.google.gson.JsonPrimitive;
import com.llamalad7.mixinextras.injector.ModifyReturnValue;
import com.llamalad7.mixinextras.sugar.Local;
import moe.nea.firmament.features.texturepack.JsonUnbakedModelFirmExtra;
import moe.nea.firmament.features.texturepack.TintOverrides;
import net.minecraft.client.render.model.json.JsonUnbakedModel;
import net.minecraft.util.Identifier;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.injection.At;
@Mixin(JsonUnbakedModel.Deserializer.class)
public class PatchJsonUnbakedModelDeserializer {
@ModifyReturnValue(method = "deserialize(Lcom/google/gson/JsonElement;Ljava/lang/reflect/Type;Lcom/google/gson/JsonDeserializationContext;)Lnet/minecraft/client/render/model/json/JsonUnbakedModel;",
at = @At("RETURN"))
private JsonUnbakedModel addHeadModel(JsonUnbakedModel original, @Local JsonObject jsonObject) {
var headModel = jsonObject.get("firmament:head_model");
var extra = ((JsonUnbakedModelFirmExtra) original);
if (headModel instanceof JsonPrimitive prim && prim.isString()) {
extra.setHeadModel_firmament(Identifier.of(prim.getAsString()));
}
var tintOverrides = jsonObject.get("firmament:tint_overrides");
if (tintOverrides instanceof JsonObject object) {
extra.setTintOverrides_firmament(TintOverrides.Companion.parse(object));
}
return original;
}
}

View File

@@ -1,22 +0,0 @@
package moe.nea.firmament.mixins.custommodels;
import com.llamalad7.mixinextras.injector.wrapoperation.Operation;
import com.llamalad7.mixinextras.injector.wrapoperation.WrapOperation;
import moe.nea.firmament.features.texturepack.CustomGlobalArmorOverrides;
import net.minecraft.client.render.entity.equipment.EquipmentModelLoader;
import net.minecraft.client.render.entity.equipment.EquipmentRenderer;
import net.minecraft.item.equipment.EquipmentModel;
import net.minecraft.util.Identifier;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.injection.At;
@Mixin(EquipmentRenderer.class)
public class PatchLegacyArmorLayerSupport {
@WrapOperation(method = "render(Lnet/minecraft/item/equipment/EquipmentModel$LayerType;Lnet/minecraft/util/Identifier;Lnet/minecraft/client/model/Model;Lnet/minecraft/item/ItemStack;Lnet/minecraft/client/util/math/MatrixStack;Lnet/minecraft/client/render/VertexConsumerProvider;ILnet/minecraft/util/Identifier;)V",
at = @At(value = "INVOKE", target = "Lnet/minecraft/client/render/entity/equipment/EquipmentModelLoader;get(Lnet/minecraft/util/Identifier;)Lnet/minecraft/item/equipment/EquipmentModel;"))
private EquipmentModel patchModelLayers(EquipmentModelLoader instance, Identifier id, Operation<EquipmentModel> original) {
var modelOverride = CustomGlobalArmorOverrides.overrideArmorLayer(id);
if (modelOverride != null) return modelOverride;
return original.call(instance, id);
}
}

View File

@@ -1,50 +0,0 @@
package moe.nea.firmament.mixins.custommodels;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.google.gson.JsonPrimitive;
import com.llamalad7.mixinextras.injector.ModifyExpressionValue;
import com.llamalad7.mixinextras.injector.ModifyReturnValue;
import com.llamalad7.mixinextras.sugar.Local;
import moe.nea.firmament.features.texturepack.CustomModelOverrideParser;
import moe.nea.firmament.features.texturepack.ModelOverrideData;
import net.minecraft.client.render.model.json.ModelOverride;
import net.minecraft.util.Identifier;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
import java.util.List;
import java.util.Map;
@Mixin(ModelOverride.Deserializer.class)
public class PatchOverrideDeserializer {
@ModifyReturnValue(
method = "deserialize(Lcom/google/gson/JsonElement;Ljava/lang/reflect/Type;Lcom/google/gson/JsonDeserializationContext;)Lnet/minecraft/client/render/model/json/ModelOverride;",
at = @At(value = "RETURN"))
private ModelOverride addCustomOverrides(ModelOverride original, @Local JsonObject jsonObject) {
var originalData = (ModelOverrideData) (Object) original;
originalData.setFirmamentOverrides(CustomModelOverrideParser.parseCustomModelOverrides(jsonObject));
return original;
}
@ModifyExpressionValue(
method = "deserializeMinPropertyValues(Lcom/google/gson/JsonObject;)Ljava/util/List;",
at = @At(value = "INVOKE", target = "Ljava/util/Map$Entry;getValue()Ljava/lang/Object;"))
private Object removeFirmamentPredicatesFromJsonIteration(Object original, @Local Map.Entry<String, JsonElement> entry) {
if (entry.getKey().startsWith("firmament:")) return new JsonPrimitive(0F);
return original;
}
@Inject(
method = "deserializeMinPropertyValues",
at = @At(value = "INVOKE", target = "Ljava/util/Map;entrySet()Ljava/util/Set;")
)
private void whatever(JsonObject object, CallbackInfoReturnable<List<ModelOverride.Condition>> cir,
@Local Map<Identifier, Float> maps) {
maps.entrySet().removeIf(it -> it.getKey().getNamespace().equals("firmament"));
}
}

View File

@@ -1,27 +0,0 @@
package moe.nea.firmament.mixins.custommodels;
import com.llamalad7.mixinextras.injector.wrapoperation.Operation;
import com.llamalad7.mixinextras.injector.wrapoperation.WrapOperation;
import moe.nea.firmament.features.texturepack.JsonUnbakedModelFirmExtra;
import net.minecraft.client.render.model.BakedModel;
import net.minecraft.client.render.model.Baker;
import net.minecraft.client.render.model.ModelBakeSettings;
import net.minecraft.client.render.model.json.JsonUnbakedModel;
import net.minecraft.client.texture.Sprite;
import net.minecraft.client.util.SpriteIdentifier;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.injection.At;
import java.util.function.Function;
/**
* @see JsonUnbakedModelDataHolder#storeExtraBaker_firmament
*/
@Mixin(targets = "net.minecraft.client.render.model.ModelBaker$BakerImpl")
public abstract class ProvideBakerToJsonUnbakedModelPatch implements Baker {
@WrapOperation(method = "bake(Lnet/minecraft/client/render/model/UnbakedModel;Lnet/minecraft/client/render/model/ModelBakeSettings;)Lnet/minecraft/client/render/model/BakedModel;", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/render/model/json/JsonUnbakedModel;bake(Ljava/util/function/Function;Lnet/minecraft/client/render/model/ModelBakeSettings;Z)Lnet/minecraft/client/render/model/BakedModel;"))
private BakedModel provideExtraBakerToModel(JsonUnbakedModel instance, Function<SpriteIdentifier, Sprite> function, ModelBakeSettings modelBakeSettings, boolean bl, Operation<BakedModel> original) {
((JsonUnbakedModelFirmExtra) instance).storeExtraBaker_firmament(this);
return original.call(instance, function, modelBakeSettings, bl);
}
}

View File

@@ -1,68 +0,0 @@
package moe.nea.firmament.mixins.custommodels;
import com.llamalad7.mixinextras.injector.ModifyExpressionValue;
import com.llamalad7.mixinextras.injector.wrapoperation.Operation;
import com.llamalad7.mixinextras.injector.wrapoperation.WrapOperation;
import com.llamalad7.mixinextras.sugar.Local;
import moe.nea.firmament.Firmament;
import moe.nea.firmament.features.texturepack.BakedOverrideData;
import moe.nea.firmament.features.texturepack.CustomSkyBlockTextures;
import moe.nea.firmament.features.texturepack.FirmamentModelPredicate;
import moe.nea.firmament.features.texturepack.ModelOverrideData;
import net.minecraft.client.render.model.json.ModelOverride;
import net.minecraft.client.render.model.json.ModelOverrideList;
import net.minecraft.item.ItemStack;
import net.minecraft.util.Identifier;
import org.objectweb.asm.Opcodes;
import org.spongepowered.asm.mixin.Final;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Shadow;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.ModifyArg;
import java.util.List;
import java.util.Objects;
@Mixin(ModelOverrideList.class)
public class TestForFirmamentOverridePredicatesPatch {
@Shadow
private Identifier[] conditionTypes;
@ModifyArg(method = "<init>(Lnet/minecraft/client/render/model/Baker;Ljava/util/List;)V",
at = @At(
value = "INVOKE", target = "Ljava/util/List;add(Ljava/lang/Object;)Z"
))
public Object onInit(
Object element,
@Local ModelOverride modelOverride
) {
var bakedOverride = (ModelOverrideList.BakedOverride) element;
var modelOverrideData = ModelOverrideData.cast(modelOverride);
BakedOverrideData.cast(bakedOverride)
.setFirmamentOverrides(modelOverrideData.getFirmamentOverrides());
if (conditionTypes.length == 0 &&
modelOverrideData.getFirmamentOverrides() != null &&
modelOverrideData.getFirmamentOverrides().length > 0) {
conditionTypes = new Identifier[]{Firmament.INSTANCE.identifier("sentinel/enforce_model_override_evaluation")};
}
return element;
}
@ModifyExpressionValue(method = "getModel", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/render/model/json/ModelOverrideList$BakedOverride;test([F)Z"))
public boolean testFirmamentOverrides(boolean originalValue,
@Local ModelOverrideList.BakedOverride bakedOverride,
@Local(argsOnly = true) ItemStack stack) {
if (!originalValue) return false;
var overrideData = (BakedOverrideData) (Object) bakedOverride;
var overrides = overrideData.getFirmamentOverrides();
if (overrides == null) return true;
if (!CustomSkyBlockTextures.TConfig.INSTANCE.getEnableModelOverrides()) return false;
for (FirmamentModelPredicate firmamentOverride : overrides) {
if (!firmamentOverride.test(stack))
return false;
}
return true;
}
}

View File

@@ -1,11 +1,13 @@
package moe.nea.firmament.events
import java.util.function.BiConsumer
import net.minecraft.client.item.ItemAssetsLoader
import net.minecraft.client.render.model.ReferencedModelsCollector
import net.minecraft.client.util.ModelIdentifier
import net.minecraft.util.Identifier
// TODO: Rename this event, since it is not really directly baking models anymore
// TODO: This event may be removed now since ItemAssetsLoader seems to load all item models now (probably to cope with servers setting the item_model component). Check whether this also applies to blocks now.
//@Deprecated(level = DeprecationLevel.ERROR, message = "This is no longer needed, since ItemAssetsLoader loads all item models.")
class BakeExtraModelsEvent(
private val addAnyModel: BiConsumer<ModelIdentifier, Identifier>,
) : FirmamentEvent() {
@@ -15,10 +17,13 @@ class BakeExtraModelsEvent(
}
fun addItemModel(modelIdentifier: ModelIdentifier) {
addNonItemModel(
modelIdentifier,
modelIdentifier.id.withPrefixedPath(ReferencedModelsCollector.ITEM_DIRECTORY))
// TODO: If this is still needed: ItemAssetsLoader.FINDER
// addNonItemModel(
// modelIdentifier,
// modelIdentifier.id.withPrefixedPath())
}
// @Deprecated(level = DeprecationLevel.ERROR, message = "This is no longer needed, since ItemAssetsLoader loads all item models.")
@Suppress("DEPRECATION")
companion object : FirmamentEventBus<BakeExtraModelsEvent>()
}

View File

@@ -1,38 +1,23 @@
package moe.nea.firmament.events
import java.util.Optional
import kotlin.jvm.optionals.getOrNull
import net.minecraft.client.render.item.ItemModels
import net.minecraft.client.render.model.BakedModel
import net.minecraft.client.util.ModelIdentifier
import net.minecraft.item.ItemStack
import moe.nea.firmament.util.ErrorUtil
import moe.nea.firmament.util.collections.WeakCache
import net.minecraft.util.Identifier
// TODO: assert an order on these events
data class CustomItemModelEvent(
val itemStack: ItemStack,
var overrideModel: ModelIdentifier? = null,
var overrideModel: Identifier? = null,
) : FirmamentEvent() {
companion object : FirmamentEventBus<CustomItemModelEvent>() {
val cache =
WeakCache.memoize<ItemStack, ItemModels, Optional<BakedModel>>("CustomItemModels") { stack, models ->
val modelId = getModelIdentifier(stack) ?: return@memoize Optional.empty()
ErrorUtil.softCheck("Model Id needs to have an inventory variant", modelId.variant() == "inventory")
val bakedModel = models.getModel(modelId.id)
if (bakedModel == null || bakedModel === models.missingModelSupplier.get()) return@memoize Optional.empty()
Optional.of(bakedModel)
}
@JvmStatic
fun getModelIdentifier(itemStack: ItemStack?): ModelIdentifier? {
fun getModelIdentifier(itemStack: ItemStack?): Identifier? {
// TODO: Re-add memoization and add an error / warning if the model does not exist
if (itemStack == null) return null
return publish(CustomItemModelEvent(itemStack)).overrideModel
}
}
@JvmStatic
fun getModel(itemStack: ItemStack?, thing: ItemModels): BakedModel? {
if (itemStack == null) return null
return cache.invoke(itemStack, thing).getOrNull()
}
fun overrideIfExists(overrideModel: Identifier) {
TODO()
}
}

View File

@@ -29,7 +29,6 @@ import moe.nea.firmament.features.inventory.buttons.InventoryButtons
import moe.nea.firmament.features.inventory.storageoverlay.StorageOverlay
import moe.nea.firmament.features.mining.PickaxeAbility
import moe.nea.firmament.features.mining.PristineProfitTracker
import moe.nea.firmament.features.texturepack.CustomSkyBlockTextures
import moe.nea.firmament.features.world.FairySouls
import moe.nea.firmament.features.world.Waypoints
import moe.nea.firmament.util.data.DataHolder
@@ -70,7 +69,6 @@ object FeatureManager : DataHolder<FeatureManager.Config>(serializer(), "feature
loadFeature(QuickCommands)
loadFeature(PetFeatures)
loadFeature(SaveCursorPosition)
loadFeature(CustomSkyBlockTextures)
loadFeature(PriceData)
loadFeature(Fixes)
loadFeature(DianaWaypoints)

View File

@@ -1,5 +1,3 @@
package moe.nea.firmament.features.chat
import io.ktor.client.request.get
@@ -7,16 +5,15 @@ import io.ktor.client.statement.bodyAsChannel
import io.ktor.utils.io.jvm.javaio.toInputStream
import java.net.URL
import java.util.Collections
import java.util.concurrent.atomic.AtomicInteger
import moe.nea.jarvis.api.Point
import kotlinx.coroutines.Deferred
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.async
import kotlin.math.min
import net.minecraft.client.gui.screen.ChatScreen
import net.minecraft.client.render.RenderLayer
import net.minecraft.client.texture.NativeImage
import net.minecraft.client.texture.NativeImageBackedTexture
import net.minecraft.scoreboard.ScoreboardCriterion.RenderType
import net.minecraft.text.ClickEvent
import net.minecraft.text.HoverEvent
import net.minecraft.text.Style
@@ -35,130 +32,132 @@ import moe.nea.firmament.util.transformEachRecursively
import moe.nea.firmament.util.unformattedString
object ChatLinks : FirmamentFeature {
override val identifier: String
get() = "chat-links"
override val identifier: String
get() = "chat-links"
object TConfig : ManagedConfig(identifier, Category.CHAT) {
val enableLinks by toggle("links-enabled") { true }
val imageEnabled by toggle("image-enabled") { true }
val allowAllHosts by toggle("allow-all-hosts") { false }
val allowedHosts by string("allowed-hosts") { "cdn.discordapp.com,media.discordapp.com,media.discordapp.net,i.imgur.com" }
val actualAllowedHosts get() = allowedHosts.split(",").map { it.trim() }
val position by position("position", 16 * 20, 9 * 20) { Point(0.0, 0.0) }
}
object TConfig : ManagedConfig(identifier, Category.CHAT) {
val enableLinks by toggle("links-enabled") { true }
val imageEnabled by toggle("image-enabled") { true }
val allowAllHosts by toggle("allow-all-hosts") { false }
val allowedHosts by string("allowed-hosts") { "cdn.discordapp.com,media.discordapp.com,media.discordapp.net,i.imgur.com" }
val actualAllowedHosts get() = allowedHosts.split(",").map { it.trim() }
val position by position("position", 16 * 20, 9 * 20) { Point(0.0, 0.0) }
}
private fun isHostAllowed(host: String) =
TConfig.allowAllHosts || TConfig.actualAllowedHosts.any { it.equals(host, ignoreCase = true) }
private fun isHostAllowed(host: String) =
TConfig.allowAllHosts || TConfig.actualAllowedHosts.any { it.equals(host, ignoreCase = true) }
private fun isUrlAllowed(url: String) = isHostAllowed(url.removePrefix("https://").substringBefore("/"))
private fun isUrlAllowed(url: String) = isHostAllowed(url.removePrefix("https://").substringBefore("/"))
override val config get() = TConfig
val urlRegex = "https://[^. ]+\\.[^ ]+(\\.?( |$))".toRegex()
override val config get() = TConfig
val urlRegex = "https://[^. ]+\\.[^ ]+(\\.?( |$))".toRegex()
val nextTexId = AtomicInteger(0)
data class Image(
val texture: Identifier,
val width: Int,
val height: Int,
)
data class Image(
val texture: Identifier,
val width: Int,
val height: Int,
)
val imageCache: MutableMap<String, Deferred<Image?>> =
Collections.synchronizedMap(mutableMapOf<String, Deferred<Image?>>())
val imageCache: MutableMap<String, Deferred<Image?>> =
Collections.synchronizedMap(mutableMapOf<String, Deferred<Image?>>())
private fun tryCacheUrl(url: String) {
if (!isUrlAllowed(url)) {
return
}
if (url in imageCache) {
return
}
imageCache[url] = Firmament.coroutineScope.async {
try {
val response = Firmament.httpClient.get(URL(url))
if (response.status.value == 200) {
val inputStream = response.bodyAsChannel().toInputStream(Firmament.globalJob)
val image = NativeImage.read(inputStream)
val texture = MC.textureManager.registerDynamicTexture(
"dynamic_image_preview",
NativeImageBackedTexture(image)
)
Image(texture, image.width, image.height)
} else
null
} catch (exc: Exception) {
exc.printStackTrace()
null
}
}
}
private fun tryCacheUrl(url: String) {
if (!isUrlAllowed(url)) {
return
}
if (url in imageCache) {
return
}
imageCache[url] = Firmament.coroutineScope.async {
try {
val response = Firmament.httpClient.get(URL(url))
if (response.status.value == 200) {
val inputStream = response.bodyAsChannel().toInputStream(Firmament.globalJob)
val image = NativeImage.read(inputStream)
val texId = Firmament.identifier("dynamic_image_preview${nextTexId.getAndIncrement()}")
MC.textureManager.registerTexture(
texId,
NativeImageBackedTexture(image)
)
Image(texId, image.width, image.height)
} else
null
} catch (exc: Exception) {
exc.printStackTrace()
null
}
}
}
val imageExtensions = listOf("jpg", "png", "gif", "jpeg")
fun isImageUrl(url: String): Boolean {
return (url.substringAfterLast('.').lowercase() in imageExtensions)
}
val imageExtensions = listOf("jpg", "png", "gif", "jpeg")
fun isImageUrl(url: String): Boolean {
return (url.substringAfterLast('.').lowercase() in imageExtensions)
}
@Subscribe
@OptIn(ExperimentalCoroutinesApi::class)
fun onRender(it: ScreenRenderPostEvent) {
if (!TConfig.imageEnabled) return
if (it.screen !is ChatScreen) return
val hoveredComponent =
MC.inGameHud.chatHud.getTextStyleAt(it.mouseX.toDouble(), it.mouseY.toDouble()) ?: return
val hoverEvent = hoveredComponent.hoverEvent ?: return
val value = hoverEvent.getValue(HoverEvent.Action.SHOW_TEXT) ?: return
val url = urlRegex.matchEntire(value.unformattedString)?.groupValues?.get(0) ?: return
if (!isImageUrl(url)) return
val imageFuture = imageCache[url] ?: return
if (!imageFuture.isCompleted) return
val image = imageFuture.getCompleted() ?: return
it.drawContext.matrices.push()
val pos = TConfig.position
pos.applyTransformations(it.drawContext.matrices)
val scale = min(1F, min((9 * 20F) / image.height, (16 * 20F) / image.width))
it.drawContext.matrices.scale(scale, scale, 1F)
it.drawContext.drawTexture(
image.texture,
0,
0,
1F,
1F,
image.width,
image.height,
image.width,
image.height,
)
it.drawContext.matrices.pop()
}
@Subscribe
@OptIn(ExperimentalCoroutinesApi::class)
fun onRender(it: ScreenRenderPostEvent) {
if (!TConfig.imageEnabled) return
if (it.screen !is ChatScreen) return
val hoveredComponent =
MC.inGameHud.chatHud.getTextStyleAt(it.mouseX.toDouble(), it.mouseY.toDouble()) ?: return
val hoverEvent = hoveredComponent.hoverEvent ?: return
val value = hoverEvent.getValue(HoverEvent.Action.SHOW_TEXT) ?: return
val url = urlRegex.matchEntire(value.unformattedString)?.groupValues?.get(0) ?: return
if (!isImageUrl(url)) return
val imageFuture = imageCache[url] ?: return
if (!imageFuture.isCompleted) return
val image = imageFuture.getCompleted() ?: return
it.drawContext.matrices.push()
val pos = TConfig.position
pos.applyTransformations(it.drawContext.matrices)
val scale = min(1F, min((9 * 20F) / image.height, (16 * 20F) / image.width))
it.drawContext.matrices.scale(scale, scale, 1F)
it.drawContext.drawTexture(
image.texture,
0,
0,
1F,
1F,
image.width,
image.height,
image.width,
image.height,
)
it.drawContext.matrices.pop()
}
@Subscribe
fun onModifyChat(it: ModifyChatEvent) {
if (!TConfig.enableLinks) return
it.replaceWith = it.replaceWith.transformEachRecursively { child ->
val text = child.string
if ("://" !in text) return@transformEachRecursively child
val s = Text.empty().setStyle(child.style)
var index = 0
while (index < text.length) {
val nextMatch = urlRegex.find(text, index)
if (nextMatch == null) {
s.append(Text.literal(text.substring(index, text.length)))
break
}
val range = nextMatch.groups[0]!!.range
val url = nextMatch.groupValues[0]
s.append(Text.literal(text.substring(index, range.first)))
s.append(
Text.literal(url).setStyle(
Style.EMPTY.withUnderline(true).withColor(
Formatting.AQUA
).withHoverEvent(HoverEvent(HoverEvent.Action.SHOW_TEXT, Text.literal(url)))
.withClickEvent(ClickEvent(ClickEvent.Action.OPEN_URL, url))
)
)
if (isImageUrl(url))
tryCacheUrl(url)
index = range.last + 1
}
s
}
}
@Subscribe
fun onModifyChat(it: ModifyChatEvent) {
if (!TConfig.enableLinks) return
it.replaceWith = it.replaceWith.transformEachRecursively { child ->
val text = child.string
if ("://" !in text) return@transformEachRecursively child
val s = Text.empty().setStyle(child.style)
var index = 0
while (index < text.length) {
val nextMatch = urlRegex.find(text, index)
if (nextMatch == null) {
s.append(Text.literal(text.substring(index, text.length)))
break
}
val range = nextMatch.groups[0]!!.range
val url = nextMatch.groupValues[0]
s.append(Text.literal(text.substring(index, range.first)))
s.append(
Text.literal(url).setStyle(
Style.EMPTY.withUnderline(true).withColor(
Formatting.AQUA
).withHoverEvent(HoverEvent(HoverEvent.Action.SHOW_TEXT, Text.literal(url)))
.withClickEvent(ClickEvent(ClickEvent.Action.OPEN_URL, url))
)
)
if (isImageUrl(url))
tryCacheUrl(url)
index = range.last + 1
}
s
}
}
}

View File

@@ -5,6 +5,7 @@ import kotlin.jvm.optionals.getOrNull
import net.minecraft.block.SkullBlock
import net.minecraft.block.entity.SkullBlockEntity
import net.minecraft.component.DataComponentTypes
import net.minecraft.component.type.ProfileComponent
import net.minecraft.entity.Entity
import net.minecraft.entity.LivingEntity
import net.minecraft.item.ItemStack
@@ -12,6 +13,7 @@ import net.minecraft.item.Items
import net.minecraft.nbt.NbtOps
import net.minecraft.text.Text
import net.minecraft.text.TextCodecs
import net.minecraft.util.Identifier
import net.minecraft.util.hit.BlockHitResult
import net.minecraft.util.hit.EntityHitResult
import net.minecraft.util.hit.HitResult
@@ -23,7 +25,6 @@ import moe.nea.firmament.events.ScreenChangeEvent
import moe.nea.firmament.events.TickEvent
import moe.nea.firmament.events.WorldKeyboardEvent
import moe.nea.firmament.features.FirmamentFeature
import moe.nea.firmament.features.texturepack.CustomSkyBlockTextures
import moe.nea.firmament.gui.config.ManagedConfig
import moe.nea.firmament.mixins.accessor.AccessorHandledScreen
import moe.nea.firmament.util.ClipboardUtils
@@ -101,6 +102,8 @@ object PowerUserTools : FirmamentFeature {
}
}
// TODO: leak this through some other way, maybe.
lateinit var getSkullId: (profile: ProfileComponent) -> Identifier?
@Subscribe
fun copyInventoryInfo(it: HandledScreenKeyPressedEvent) {
@@ -116,7 +119,7 @@ object PowerUserTools : FirmamentFeature {
lastCopiedStack =
Pair(item, Text.stringifiedTranslatable("firmament.tooltip.copied.skyblockid", sbId.neuItem))
} else if (it.matches(TConfig.copyTexturePackId)) {
val model = CustomItemModelEvent.getModelIdentifier(item)
val model = CustomItemModelEvent.getModelIdentifier(item) // TODO: remove global texture overrides, maybe
if (model == null) {
lastCopiedStack = Pair(item, Text.translatable("firmament.tooltip.copied.modelid.fail"))
return
@@ -146,7 +149,7 @@ object PowerUserTools : FirmamentFeature {
lastCopiedStack = Pair(item, Text.translatable("firmament.tooltip.copied.skull-id.fail.no-profile"))
return
}
val skullTexture = CustomSkyBlockTextures.getSkullTexture(profile)
val skullTexture = getSkullId(profile)
if (skullTexture == null) {
lastCopiedStack = Pair(item, Text.translatable("firmament.tooltip.copied.skull-id.fail.no-texture"))
return
@@ -179,7 +182,7 @@ object PowerUserTools : FirmamentFeature {
MC.sendChat(Text.translatable("firmament.tooltip.copied.skull.fail"))
return
}
val id = CustomSkyBlockTextures.getSkullTexture(entity.owner!!)
val id = getSkullId(entity.owner!!)
if (id == null) {
MC.sendChat(Text.translatable("firmament.tooltip.copied.skull.fail"))
} else {

View File

@@ -1,30 +0,0 @@
package moe.nea.firmament.features.texturepack
import net.fabricmc.fabric.api.renderer.v1.model.WrapperBakedModel as WrapperBakedModelFabric
import net.minecraft.client.render.model.BakedModel
import net.minecraft.client.render.model.WrapperBakedModel
import moe.nea.firmament.util.ErrorUtil
interface BakedModelExtra {
companion object {
@JvmStatic
fun cast(originalModel: BakedModel): BakedModelExtra? {
var p = originalModel
for (i in 0..256) {
p = when (p) {
is BakedModelExtra -> return p
is WrapperBakedModel -> p.wrapped
is WrapperBakedModelFabric -> WrapperBakedModelFabric.unwrap(p)
else -> break
}
}
ErrorUtil.softError("Could not find a baked model for $originalModel")
return null
}
}
var tintOverrides_firmament: TintOverrides?
fun getHeadModel_firmament(): BakedModel?
fun setHeadModel_firmament(headModel: BakedModel?)
}

View File

@@ -1,14 +0,0 @@
package moe.nea.firmament.features.texturepack
import net.minecraft.client.render.model.json.ModelOverrideList
interface BakedOverrideData {
fun getFirmamentOverrides(): Array<FirmamentModelPredicate>?
fun setFirmamentOverrides(overrides: Array<FirmamentModelPredicate>?)
companion object{
@Suppress("CAST_NEVER_SUCCEEDS")
@JvmStatic
fun cast(bakedOverride: ModelOverrideList.BakedOverride): BakedOverrideData = bakedOverride as BakedOverrideData
}
}

View File

@@ -1,82 +0,0 @@
package moe.nea.firmament.features.texturepack
import com.google.gson.JsonObject
import kotlinx.serialization.KSerializer
import kotlinx.serialization.descriptors.SerialDescriptor
import kotlinx.serialization.encoding.Decoder
import kotlinx.serialization.encoding.Encoder
import moe.nea.firmament.features.texturepack.predicates.AndPredicate
import moe.nea.firmament.features.texturepack.predicates.DisplayNamePredicate
import moe.nea.firmament.features.texturepack.predicates.ExtraAttributesPredicate
import moe.nea.firmament.features.texturepack.predicates.ItemPredicate
import moe.nea.firmament.features.texturepack.predicates.LorePredicate
import moe.nea.firmament.features.texturepack.predicates.NotPredicate
import moe.nea.firmament.features.texturepack.predicates.OrPredicate
import moe.nea.firmament.features.texturepack.predicates.PetPredicate
import net.minecraft.item.ItemStack
import net.minecraft.util.Identifier
object CustomModelOverrideParser {
object FirmamentRootPredicateSerializer : KSerializer<FirmamentModelPredicate> {
val delegateSerializer = kotlinx.serialization.json.JsonObject.serializer()
override val descriptor: SerialDescriptor
get() = SerialDescriptor("FirmamentModelRootPredicate", delegateSerializer.descriptor)
override fun deserialize(decoder: Decoder): FirmamentModelPredicate {
val json = decoder.decodeSerializableValue(delegateSerializer).intoGson() as JsonObject
return AndPredicate(parsePredicates(json).toTypedArray())
}
override fun serialize(encoder: Encoder, value: FirmamentModelPredicate) {
TODO("Cannot serialize firmament predicates")
}
}
val predicateParsers = mutableMapOf<Identifier, FirmamentModelPredicateParser>()
fun registerPredicateParser(name: String, parser: FirmamentModelPredicateParser) {
predicateParsers[Identifier.of("firmament", name)] = parser
}
init {
registerPredicateParser("display_name", DisplayNamePredicate.Parser)
registerPredicateParser("lore", LorePredicate.Parser)
registerPredicateParser("all", AndPredicate.Parser)
registerPredicateParser("any", OrPredicate.Parser)
registerPredicateParser("not", NotPredicate.Parser)
registerPredicateParser("item", ItemPredicate.Parser)
registerPredicateParser("extra_attributes", ExtraAttributesPredicate.Parser)
registerPredicateParser("pet", PetPredicate.Parser)
}
private val neverPredicate = listOf(
object : FirmamentModelPredicate {
override fun test(stack: ItemStack): Boolean {
return false
}
}
)
fun parsePredicates(predicates: JsonObject): List<FirmamentModelPredicate> {
val parsedPredicates = mutableListOf<FirmamentModelPredicate>()
for (predicateName in predicates.keySet()) {
if (!predicateName.startsWith("firmament:")) continue
val identifier = Identifier.of(predicateName)
val parser = predicateParsers[identifier] ?: return neverPredicate
val parsedPredicate = parser.parse(predicates[predicateName]) ?: return neverPredicate
parsedPredicates.add(parsedPredicate)
}
return parsedPredicates
}
@JvmStatic
fun parseCustomModelOverrides(jsonObject: JsonObject): Array<FirmamentModelPredicate>? {
val predicates = (jsonObject["predicate"] as? JsonObject) ?: return null
val parsedPredicates = parsePredicates(predicates)
if (parsedPredicates.isEmpty())
return null
return parsedPredicates.toTypedArray()
}
}

View File

@@ -1,135 +0,0 @@
package moe.nea.firmament.features.texturepack
import com.mojang.authlib.minecraft.MinecraftProfileTexture
import com.mojang.authlib.properties.Property
import java.util.Optional
import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable
import kotlin.jvm.optionals.getOrNull
import net.minecraft.block.SkullBlock
import net.minecraft.client.MinecraftClient
import net.minecraft.client.render.RenderLayer
import net.minecraft.client.util.ModelIdentifier
import net.minecraft.component.type.ProfileComponent
import net.minecraft.util.Identifier
import moe.nea.firmament.annotations.Subscribe
import moe.nea.firmament.events.BakeExtraModelsEvent
import moe.nea.firmament.events.CustomItemModelEvent
import moe.nea.firmament.events.FinalizeResourceManagerEvent
import moe.nea.firmament.events.TickEvent
import moe.nea.firmament.features.FirmamentFeature
import moe.nea.firmament.gui.config.ManagedConfig
import moe.nea.firmament.util.collections.WeakCache
import moe.nea.firmament.util.mc.decodeProfileTextureProperty
import moe.nea.firmament.util.skyBlockId
object CustomSkyBlockTextures : FirmamentFeature {
override val identifier: String
get() = "custom-skyblock-textures"
object TConfig : ManagedConfig(identifier, Category.INTEGRATIONS) { // TODO: should this be its own thing?
val enabled by toggle("enabled") { true }
val skullsEnabled by toggle("skulls-enabled") { true }
val cacheForever by toggle("cache-forever") { true }
val cacheDuration by integer("cache-duration", 0, 100) { 1 }
val enableModelOverrides by toggle("model-overrides") { true }
val enableArmorOverrides by toggle("armor-overrides") { true }
val enableBlockOverrides by toggle("block-overrides") { true }
val enableLegacyCIT by toggle("legacy-cit") { true }
val allowRecoloringUiText by toggle("recolor-text") { true }
}
override val config: ManagedConfig
get() = TConfig
val allItemCaches by lazy {
listOf(
CustomItemModelEvent.cache.cache,
skullTextureCache.cache,
CustomGlobalTextures.overrideCache.cache,
CustomGlobalArmorOverrides.overrideCache.cache
)
}
fun clearAllCaches() {
allItemCaches.forEach(WeakCache<*, *, *>::clear)
}
@Subscribe
fun onTick(it: TickEvent) {
if (TConfig.cacheForever) return
if (TConfig.cacheDuration < 1 || it.tickCount % TConfig.cacheDuration == 0) {
clearAllCaches()
}
}
@Subscribe
fun onStart(event: FinalizeResourceManagerEvent) {
event.registerOnApply("Clear firmament CIT caches") {
clearAllCaches()
}
}
@Subscribe
fun bakeCustomFirmModels(event: BakeExtraModelsEvent) {
val resources =
MinecraftClient.getInstance().resourceManager.findResources("models/item"
) { it: Identifier ->
"firmskyblock" == it.namespace && it.path
.endsWith(".json")
}
for (identifier in resources.keys) {
val modelId = ModelIdentifier.ofInventoryVariant(
Identifier.of(
"firmskyblock",
identifier.path.substring(
"models/item/".length,
identifier.path.length - ".json".length),
))
event.addItemModel(modelId)
}
}
@Subscribe
fun onCustomModelId(it: CustomItemModelEvent) {
if (!TConfig.enabled) return
val id = it.itemStack.skyBlockId ?: return
it.overrideModel = ModelIdentifier.ofInventoryVariant(Identifier.of("firmskyblock", id.identifier.path))
}
private val skullTextureCache =
WeakCache.memoize<ProfileComponent, Optional<Identifier>>("SkullTextureCache") { component ->
val id = getSkullTexture(component) ?: return@memoize Optional.empty()
if (!MinecraftClient.getInstance().resourceManager.getResource(id).isPresent) {
return@memoize Optional.empty()
}
return@memoize Optional.of(id)
}
private val mcUrlRegex = "https?://textures.minecraft.net/texture/([a-fA-F0-9]+)".toRegex()
fun getSkullId(textureProperty: Property): String? {
val texture = decodeProfileTextureProperty(textureProperty) ?: return null
val textureUrl =
texture.textures[MinecraftProfileTexture.Type.SKIN]?.url ?: return null
val mcUrlData = mcUrlRegex.matchEntire(textureUrl) ?: return null
return mcUrlData.groupValues[1]
}
fun getSkullTexture(profile: ProfileComponent): Identifier? {
val id = getSkullId(profile.properties["textures"].firstOrNull() ?: return null) ?: return null
return Identifier.of("firmskyblock", "textures/placedskull/$id.png")
}
fun modifySkullTexture(
type: SkullBlock.SkullType?,
component: ProfileComponent?,
cir: CallbackInfoReturnable<RenderLayer>
) {
if (type != SkullBlock.Type.PLAYER) return
if (!TConfig.skullsEnabled) return
if (component == null) return
val n = skullTextureCache.invoke(component).getOrNull() ?: return
cir.returnValue = RenderLayer.getEntityTranslucent(n)
}
}

View File

@@ -1,16 +0,0 @@
package moe.nea.firmament.features.texturepack
import net.minecraft.client.render.model.Baker
import net.minecraft.util.Identifier
interface JsonUnbakedModelFirmExtra {
fun storeExtraBaker_firmament(baker: Baker)
fun setHeadModel_firmament(identifier: Identifier?)
fun getHeadModel_firmament(): Identifier?
fun setTintOverrides_firmament(tintOverrides: TintOverrides?)
fun getTintOverrides_firmament(): TintOverrides
}

View File

@@ -1,15 +0,0 @@
package moe.nea.firmament.features.texturepack
import net.minecraft.client.render.model.json.ModelOverride
interface ModelOverrideData {
companion object {
@JvmStatic
@Suppress("CAST_NEVER_SUCCEEDS")
fun cast(override: ModelOverride) = override as ModelOverrideData
}
fun getFirmamentOverrides(): Array<FirmamentModelPredicate>?
fun setFirmamentOverrides(overrides: Array<FirmamentModelPredicate>?)
}

View File

@@ -8,6 +8,7 @@ import net.minecraft.block.BlockState
import net.minecraft.client.gui.screen.world.SelectWorldScreen
import net.minecraft.component.type.MapIdComponent
import net.minecraft.entity.Entity
import net.minecraft.entity.boss.dragon.EnderDragonPart
import net.minecraft.entity.damage.DamageSource
import net.minecraft.entity.player.PlayerEntity
import net.minecraft.fluid.Fluid
@@ -262,6 +263,10 @@ class FakeWorld(
return null
}
override fun getEnderDragonParts(): MutableCollection<EnderDragonPart> {
return mutableListOf()
}
override fun getTickManager(): TickManager {
return TickManager()
}

View File

@@ -1,4 +1,3 @@
package moe.nea.firmament.repo
import java.io.InputStream
@@ -21,86 +20,86 @@ import net.minecraft.resource.ResourcePackInfo
import net.minecraft.resource.ResourcePackSource
import net.minecraft.resource.ResourceType
import net.minecraft.resource.metadata.ResourceMetadata
import net.minecraft.resource.metadata.ResourceMetadataReader
import net.minecraft.resource.metadata.ResourceMetadataSerializer
import net.minecraft.text.Text
import net.minecraft.util.Identifier
import net.minecraft.util.PathUtil
import moe.nea.firmament.Firmament
class RepoModResourcePack(val basePath: Path) : ModResourcePack {
companion object {
fun append(packs: MutableList<in ModResourcePack>) {
Firmament.logger.info("Registering mod resource pack")
packs.add(RepoModResourcePack(RepoDownloadManager.repoSavedLocation))
}
companion object {
fun append(packs: MutableList<in ModResourcePack>) {
Firmament.logger.info("Registering mod resource pack")
packs.add(RepoModResourcePack(RepoDownloadManager.repoSavedLocation))
}
fun createResourceDirectly(identifier: Identifier): Optional<Resource> {
val pack = RepoModResourcePack(RepoDownloadManager.repoSavedLocation)
return Optional.of(
Resource(
pack,
pack.open(ResourceType.CLIENT_RESOURCES, identifier) ?: return Optional.empty()
) {
val base =
pack.open(ResourceType.CLIENT_RESOURCES, identifier.withPath(identifier.path + ".mcmeta"))
if (base == null)
ResourceMetadata.NONE
else
NamespaceResourceManager.loadMetadata(base)
}
)
}
}
fun createResourceDirectly(identifier: Identifier): Optional<Resource> {
val pack = RepoModResourcePack(RepoDownloadManager.repoSavedLocation)
return Optional.of(
Resource(
pack,
pack.open(ResourceType.CLIENT_RESOURCES, identifier) ?: return Optional.empty()
) {
val base =
pack.open(ResourceType.CLIENT_RESOURCES, identifier.withPath(identifier.path + ".mcmeta"))
if (base == null)
ResourceMetadata.NONE
else
NamespaceResourceManager.loadMetadata(base)
}
)
}
}
override fun close() {
}
override fun close() {
}
override fun openRoot(vararg segments: String): InputSupplier<InputStream>? {
return getFile(segments)?.let { InputSupplier.create(it) }
}
override fun openRoot(vararg segments: String): InputSupplier<InputStream>? {
return getFile(segments)?.let { InputSupplier.create(it) }
}
fun getFile(segments: Array<out String>): Path? {
PathUtil.validatePath(*segments)
val path = segments.fold(basePath, Path::resolve)
if (!path.isRegularFile()) return null
return path
}
fun getFile(segments: Array<out String>): Path? {
PathUtil.validatePath(*segments)
val path = segments.fold(basePath, Path::resolve)
if (!path.isRegularFile()) return null
return path
}
override fun open(type: ResourceType?, id: Identifier): InputSupplier<InputStream>? {
if (type != ResourceType.CLIENT_RESOURCES) return null
if (id.namespace != "neurepo") return null
val file = getFile(id.path.split("/").toTypedArray())
return file?.let { InputSupplier.create(it) }
}
override fun open(type: ResourceType?, id: Identifier): InputSupplier<InputStream>? {
if (type != ResourceType.CLIENT_RESOURCES) return null
if (id.namespace != "neurepo") return null
val file = getFile(id.path.split("/").toTypedArray())
return file?.let { InputSupplier.create(it) }
}
override fun findResources(
type: ResourceType?,
namespace: String,
prefix: String,
consumer: ResourcePack.ResultConsumer
) {
if (namespace != "neurepo") return
if (type != ResourceType.CLIENT_RESOURCES) return
override fun findResources(
type: ResourceType?,
namespace: String,
prefix: String,
consumer: ResourcePack.ResultConsumer
) {
if (namespace != "neurepo") return
if (type != ResourceType.CLIENT_RESOURCES) return
val prefixPath = basePath.resolve(prefix)
if (!prefixPath.exists())
return
Files.walk(prefixPath)
.asSequence()
.map { it.relativeTo(basePath) }
.forEach {
consumer.accept(Identifier.of("neurepo", it.toString()), InputSupplier.create(it))
}
}
val prefixPath = basePath.resolve(prefix)
if (!prefixPath.exists())
return
Files.walk(prefixPath)
.asSequence()
.map { it.relativeTo(basePath) }
.forEach {
consumer.accept(Identifier.of("neurepo", it.toString()), InputSupplier.create(it))
}
}
override fun getNamespaces(type: ResourceType?): Set<String> {
if (type != ResourceType.CLIENT_RESOURCES) return emptySet()
return setOf("neurepo")
}
override fun getNamespaces(type: ResourceType?): Set<String> {
if (type != ResourceType.CLIENT_RESOURCES) return emptySet()
return setOf("neurepo")
}
override fun <T> parseMetadata(metaReader: ResourceMetadataReader<T>): T? {
return AbstractFileResourcePack.parseMetadata(
metaReader, """
override fun <T : Any?> parseMetadata(metadataSerializer: ResourceMetadataSerializer<T>?): T? {
return AbstractFileResourcePack.parseMetadata(
metadataSerializer, """
{
"pack": {
"pack_format": 12,
@@ -108,19 +107,20 @@ class RepoModResourcePack(val basePath: Path) : ModResourcePack {
}
}
""".trimIndent().byteInputStream()
)
}
)
}
override fun getInfo(): ResourcePackInfo {
return ResourcePackInfo("neurepo", Text.literal("NEU Repo"), ResourcePackSource.BUILTIN, Optional.empty())
}
override fun getFabricModMetadata(): ModMetadata {
return FabricLoader.getInstance().getModContainer("firmament")
.get().metadata
}
override fun getInfo(): ResourcePackInfo {
return ResourcePackInfo("neurepo", Text.literal("NEU Repo"), ResourcePackSource.BUILTIN, Optional.empty())
}
override fun createOverlay(overlay: String): ModResourcePack {
return RepoModResourcePack(basePath.resolve(overlay))
}
override fun getFabricModMetadata(): ModMetadata {
return FabricLoader.getInstance().getModContainer("firmament")
.get().metadata
}
override fun createOverlay(overlay: String): ModResourcePack {
return RepoModResourcePack(basePath.resolve(overlay))
}
}

View File

@@ -4,12 +4,6 @@ accessible class net/minecraft/client/render/RenderLayer$MultiPhaseParameters
accessible class net/minecraft/client/font/TextRenderer$Drawer
accessible field net/minecraft/client/gui/hud/InGameHud SCOREBOARD_ENTRY_COMPARATOR Ljava/util/Comparator;
accessible field net/minecraft/client/render/item/HeldItemRenderer itemRenderer Lnet/minecraft/client/render/item/ItemRenderer;
accessible field net/minecraft/client/render/item/ItemModels missingModelSupplier Ljava/util/function/Supplier;
mutable field net/minecraft/client/render/model/json/ModelOverrideList conditionTypes [Lnet/minecraft/util/Identifier;
accessible class net/minecraft/client/render/model/json/ModelOverride$Deserializer
accessible class net/minecraft/client/render/model/json/ModelOverrideList$BakedOverride
accessible field net/minecraft/entity/mob/CreeperEntity CHARGED Lnet/minecraft/entity/data/TrackedData;
accessible method net/minecraft/entity/decoration/ArmorStandEntity setSmall (Z)V
accessible field net/minecraft/entity/passive/AbstractHorseEntity items Lnet/minecraft/inventory/SimpleInventory;
@@ -23,16 +17,3 @@ mutable field net/minecraft/screen/slot/Slot y I
accessible field net/minecraft/entity/player/PlayerEntity PLAYER_MODEL_PARTS Lnet/minecraft/entity/data/TrackedData;
accessible field net/minecraft/client/render/WorldRenderer chunks Lnet/minecraft/client/render/BuiltChunkStorage;
# Fix package-private access methods
accessible method net/minecraft/registry/entry/RegistryEntry$Reference setRegistryKey (Lnet/minecraft/registry/RegistryKey;)V
accessible method net/minecraft/entity/LivingEntity getHitbox ()Lnet/minecraft/util/math/Box;
accessible method net/minecraft/registry/entry/RegistryEntryList$Named <init> (Lnet/minecraft/registry/entry/RegistryEntryOwner;Lnet/minecraft/registry/tag/TagKey;)V
accessible method net/minecraft/registry/entry/RegistryEntry$Reference setValue (Ljava/lang/Object;)V
accessible field net/minecraft/client/render/model/WrapperBakedModel wrapped Lnet/minecraft/client/render/model/BakedModel;
accessible method net/minecraft/entity/passive/TameableEntity isInSameTeam (Lnet/minecraft/entity/Entity;)Z
accessible method net/minecraft/entity/Entity isInSameTeam (Lnet/minecraft/entity/Entity;)Z
accessible method net/minecraft/registry/entry/RegistryEntry$Reference setTags (Ljava/util/Collection;)V
accessible method net/minecraft/registry/entry/RegistryEntryList$Named setEntries (Ljava/util/List;)V
accessible method net/minecraft/world/biome/source/util/VanillaBiomeParameters writeOverworldBiomeParameters (Ljava/util/function/Consumer;)V
accessible method net/minecraft/world/gen/densityfunction/DensityFunctions createSurfaceNoiseRouter (Lnet/minecraft/registry/RegistryEntryLookup;Lnet/minecraft/registry/RegistryEntryLookup;ZZ)Lnet/minecraft/world/gen/noise/NoiseRouter;

View File

@@ -46,9 +46,9 @@ import moe.nea.firmament.util.json.SingletonSerializableList
object CustomBlockTextures {
@Serializable
data class CustomBlockOverride(
val modes: @Serializable(SingletonSerializableList::class) List<String>,
val area: List<Area>? = null,
val replacements: Map<Identifier, Replacement>,
val modes: @Serializable(SingletonSerializableList::class) List<String>,
val area: List<Area>? = null,
val replacements: Map<Identifier, Replacement>,
)
@Serializable(with = Replacement.Serializer::class)
@@ -135,8 +135,8 @@ object CustomBlockTextures {
)
data class BlockReplacement(
val checks: List<Area>?,
val replacement: Replacement,
val checks: List<Area>?,
val replacement: Replacement,
) {
val roughCheck by lazy(LazyThreadSafetyMode.NONE) {
if (checks == null || checks.size < 3) return@lazy null

View File

@@ -8,8 +8,12 @@ import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
import kotlinx.serialization.Transient
import kotlinx.serialization.UseSerializers
import net.minecraft.client.render.entity.equipment.EquipmentModel
import net.minecraft.component.type.EquippableComponent
import net.minecraft.entity.EquipmentSlot
import net.minecraft.item.ItemStack
import net.minecraft.item.equipment.EquipmentModel
import net.minecraft.item.equipment.EquipmentAssetKeys
import net.minecraft.registry.RegistryKey
import net.minecraft.resource.ResourceManager
import net.minecraft.resource.SinglePreparationResourceReloader
import net.minecraft.util.Identifier
@@ -20,6 +24,7 @@ import moe.nea.firmament.events.FinalizeResourceManagerEvent
import moe.nea.firmament.features.texturepack.CustomGlobalTextures.logger
import moe.nea.firmament.util.IdentifierSerializer
import moe.nea.firmament.util.collections.WeakCache
import moe.nea.firmament.util.intoOptional
import moe.nea.firmament.util.skyBlockId
object CustomGlobalArmorOverrides {
@@ -33,9 +38,9 @@ object CustomGlobalArmorOverrides {
) {
@Transient
lateinit var modelIdentifier: Identifier
fun bake() {
fun bake(manager: ResourceManager) {
modelIdentifier = bakeModel(model, layers)
overrides.forEach { it.bake() }
overrides.forEach { it.bake(manager) }
}
init {
@@ -64,23 +69,34 @@ object CustomGlobalArmorOverrides {
@Transient
lateinit var modelIdentifier: Identifier
fun bake() {
fun bake(manager: ResourceManager) {
modelIdentifier = bakeModel(model, layers)
}
}
val overrideCache = WeakCache.memoize<ItemStack, Optional<Identifier>>("ArmorOverrides") { stack ->
val id = stack.skyBlockId ?: return@memoize Optional.empty()
val override = overrides[id.neuItem] ?: return@memoize Optional.empty()
for (suboverride in override.overrides) {
if (suboverride.predicate.test(stack)) {
return@memoize Optional.of(suboverride.modelIdentifier)
}
}
return@memoize Optional.of(override.modelIdentifier)
private fun resolveComponent(slot: EquipmentSlot, model: Identifier): EquippableComponent {
return EquippableComponent(
slot,
null,
Optional.of(RegistryKey.of(EquipmentAssetKeys.REGISTRY_KEY, model)),
Optional.empty(),
Optional.empty(), false, false, false
)
}
val overrideCache =
WeakCache.memoize<ItemStack, EquipmentSlot, Optional<EquippableComponent>>("ArmorOverrides") { stack, slot ->
val id = stack.skyBlockId ?: return@memoize Optional.empty()
val override = overrides[id.neuItem] ?: return@memoize Optional.empty()
for (suboverride in override.overrides) {
if (suboverride.predicate.test(stack)) {
return@memoize resolveComponent(slot, suboverride.modelIdentifier).intoOptional()
}
}
return@memoize resolveComponent(slot, override.modelIdentifier).intoOptional()
}
var overrides: Map<String, ArmorOverride> = mapOf()
private var bakedOverrides: MutableMap<Identifier, EquipmentModel> = mutableMapOf()
private val sentinelFirmRunning = AtomicInteger()
@@ -131,20 +147,20 @@ object CustomGlobalArmorOverrides {
}
val associatedMap = overrides.flatMap { obj -> obj.itemIds.map { it to obj } }
.toMap()
associatedMap.forEach { it.value.bake(manager) }
return associatedMap
}
override fun apply(prepared: Map<String, ArmorOverride>, manager: ResourceManager, profiler: Profiler) {
bakedOverrides.clear()
prepared.forEach { it.value.bake() }
overrides = prepared
}
})
}
@JvmStatic
fun overrideArmor(itemStack: ItemStack): Optional<Identifier> {
return overrideCache.invoke(itemStack)
fun overrideArmor(itemStack: ItemStack, slot: EquipmentSlot): Optional<EquippableComponent> {
return overrideCache.invoke(itemStack, slot)
}
@JvmStatic

View File

@@ -1,18 +1,14 @@
@file:UseSerializers(IdentifierSerializer::class, CustomModelOverrideParser.FirmamentRootPredicateSerializer::class)
@file:UseSerializers(IdentifierSerializer::class, FirmamentRootPredicateSerializer::class)
package moe.nea.firmament.features.texturepack
import java.util.Optional
import java.util.concurrent.CompletableFuture
import org.slf4j.LoggerFactory
import kotlinx.serialization.Serializable
import kotlinx.serialization.UseSerializers
import kotlin.jvm.optionals.getOrNull
import net.minecraft.client.render.item.ItemModels
import net.minecraft.client.render.model.BakedModel
import net.minecraft.client.util.ModelIdentifier
import net.minecraft.item.ItemStack
import net.minecraft.resource.ResourceManager
import net.minecraft.resource.SinglePreparationResourceReloader
import net.minecraft.text.Text
@@ -21,15 +17,15 @@ import net.minecraft.util.profiler.Profiler
import moe.nea.firmament.Firmament
import moe.nea.firmament.annotations.Subscribe
import moe.nea.firmament.events.BakeExtraModelsEvent
import moe.nea.firmament.events.CustomItemModelEvent
import moe.nea.firmament.events.EarlyResourceReloadEvent
import moe.nea.firmament.events.FinalizeResourceManagerEvent
import moe.nea.firmament.events.ScreenChangeEvent
import moe.nea.firmament.events.subscription.SubscriptionOwner
import moe.nea.firmament.features.FirmamentFeature
import moe.nea.firmament.util.ErrorUtil
import moe.nea.firmament.util.IdentifierSerializer
import moe.nea.firmament.util.MC
import moe.nea.firmament.util.collections.WeakCache
import moe.nea.firmament.util.intoOptional
import moe.nea.firmament.util.json.SingletonSerializableList
import moe.nea.firmament.util.runNull
@@ -91,7 +87,7 @@ object CustomGlobalTextures : SinglePreparationResourceReloader<CustomGlobalText
}
override fun apply(prepared: CustomGuiTextureOverride, manager: ResourceManager?, profiler: Profiler?) {
this.guiClassOverrides = prepared
guiClassOverrides = prepared
}
val logger = LoggerFactory.getLogger(CustomGlobalTextures::class.java)
@@ -100,7 +96,7 @@ object CustomGlobalTextures : SinglePreparationResourceReloader<CustomGlobalText
manager.findResources("overrides/item") { it.namespace == "firmskyblock" && it.path.endsWith(".json") }
.mapNotNull {
Firmament.tryDecodeJsonFromStream<GlobalItemOverride>(it.value.inputStream).getOrElse { ex ->
logger.error("Failed to load global item override at ${it.key}", ex)
ErrorUtil.softError("Failed to load global item override at ${it.key}", ex)
null
}
}
@@ -114,12 +110,12 @@ object CustomGlobalTextures : SinglePreparationResourceReloader<CustomGlobalText
manager.getResource(Identifier.of(key.namespace, "filters/screen/${key.path}.json"))
.getOrNull()
?: return@mapNotNull runNull {
logger.error("Failed to locate screen filter at $key")
ErrorUtil.softError("Failed to locate screen filter at $key")
}
val screenFilter =
Firmament.tryDecodeJsonFromStream<ScreenFilter>(guiClassResource.inputStream)
.getOrElse { ex ->
logger.error("Failed to load screen filter at $key", ex)
ErrorUtil.softError("Failed to load screen filter at $key", ex)
return@mapNotNull null
}
ItemOverrideCollection(screenFilter, it.value.map { it.second })
@@ -139,25 +135,19 @@ object CustomGlobalTextures : SinglePreparationResourceReloader<CustomGlobalText
.filterTo(mutableSetOf()) { it.screenFilter.title.matches(newTitle) }
}
val overrideCache =
WeakCache.memoize<ItemStack, ItemModels, Optional<BakedModel>>("CustomGlobalTextureModelOverrides") { stack, models ->
matchingOverrides
.firstNotNullOfOrNull {
it.overrides
.asSequence()
.filter { it.predicate.test(stack) }
.map { models.getModel(it.model) }
.firstOrNull()
}
.intoOptional()
}
@Subscribe
fun replaceGlobalModel(event: CustomItemModelEvent) {
val override = matchingOverrides
.firstNotNullOfOrNull {
it.overrides
.asSequence()
.filter { it.predicate.test(event.itemStack) }
.map { it.model }
.firstOrNull()
}
@JvmStatic
fun replaceGlobalModel(
models: ItemModels,
stack: ItemStack,
): BakedModel? {
return overrideCache.invoke(stack, models).getOrNull()
if (override != null)
event.overrideIfExists(override)
}

View File

@@ -0,0 +1,103 @@
package moe.nea.firmament.features.texturepack
import com.google.gson.JsonObject
import com.mojang.datafixers.util.Pair
import com.mojang.serialization.Codec
import com.mojang.serialization.DataResult
import com.mojang.serialization.Decoder
import com.mojang.serialization.DynamicOps
import com.mojang.serialization.Encoder
import net.minecraft.client.render.item.model.ItemModelTypes
import net.minecraft.item.ItemStack
import net.minecraft.util.Identifier
import moe.nea.firmament.Firmament
import moe.nea.firmament.annotations.Subscribe
import moe.nea.firmament.events.FinalizeResourceManagerEvent
import moe.nea.firmament.features.texturepack.predicates.AndPredicate
import moe.nea.firmament.features.texturepack.predicates.DisplayNamePredicate
import moe.nea.firmament.features.texturepack.predicates.ExtraAttributesPredicate
import moe.nea.firmament.features.texturepack.predicates.ItemPredicate
import moe.nea.firmament.features.texturepack.predicates.LorePredicate
import moe.nea.firmament.features.texturepack.predicates.NotPredicate
import moe.nea.firmament.features.texturepack.predicates.OrPredicate
import moe.nea.firmament.features.texturepack.predicates.PetPredicate
import moe.nea.firmament.util.json.KJsonOps
object CustomModelOverrideParser {
val LEGACY_CODEC: Codec<FirmamentModelPredicate> =
Codec.of(
Encoder.error("cannot encode legacy firmament model predicates"),
object : Decoder<FirmamentModelPredicate> {
override fun <T : Any?> decode(
ops: DynamicOps<T>,
input: T
): DataResult<Pair<FirmamentModelPredicate, T>> {
try {
val pred = Firmament.json.decodeFromJsonElement(
FirmamentRootPredicateSerializer,
ops.convertTo(KJsonOps.INSTANCE, input))
return DataResult.success(Pair.of(pred, ops.empty()))
} catch (ex: Exception) {
return DataResult.error { "Could not deserialize ${ex.message}" }
}
}
}
)
val predicateParsers = mutableMapOf<Identifier, FirmamentModelPredicateParser>()
fun registerPredicateParser(name: String, parser: FirmamentModelPredicateParser) {
predicateParsers[Identifier.of("firmament", name)] = parser
}
init {
registerPredicateParser("display_name", DisplayNamePredicate.Parser)
registerPredicateParser("lore", LorePredicate.Parser)
registerPredicateParser("all", AndPredicate.Parser)
registerPredicateParser("any", OrPredicate.Parser)
registerPredicateParser("not", NotPredicate.Parser)
registerPredicateParser("item", ItemPredicate.Parser)
registerPredicateParser("extra_attributes", ExtraAttributesPredicate.Parser)
registerPredicateParser("pet", PetPredicate.Parser)
}
private val neverPredicate = listOf(
object : FirmamentModelPredicate {
override fun test(stack: ItemStack): Boolean {
return false
}
}
)
fun parsePredicates(predicates: JsonObject): List<FirmamentModelPredicate> {
val parsedPredicates = mutableListOf<FirmamentModelPredicate>()
for (predicateName in predicates.keySet()) {
if (!predicateName.startsWith("firmament:")) continue
val identifier = Identifier.of(predicateName)
val parser = predicateParsers[identifier] ?: return neverPredicate
val parsedPredicate = parser.parse(predicates[predicateName]) ?: return neverPredicate
parsedPredicates.add(parsedPredicate)
}
return parsedPredicates
}
@JvmStatic
fun parseCustomModelOverrides(jsonObject: JsonObject): Array<FirmamentModelPredicate>? {
val predicates = (jsonObject["predicate"] as? JsonObject) ?: return null
val parsedPredicates = parsePredicates(predicates)
if (parsedPredicates.isEmpty())
return null
return parsedPredicates.toTypedArray()
}
@Subscribe
fun finalizeResources(event: FinalizeResourceManagerEvent) {
ItemModelTypes.ID_MAPPER.put(
Firmament.identifier("predicates/legacy"),
PredicateModel.Unbaked.CODEC
)
}
}

View File

@@ -0,0 +1,117 @@
package moe.nea.firmament.features.texturepack
import com.mojang.authlib.minecraft.MinecraftProfileTexture
import com.mojang.authlib.properties.Property
import java.util.Optional
import org.jetbrains.annotations.Nullable
import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable
import kotlin.jvm.optionals.getOrNull
import net.minecraft.block.SkullBlock
import net.minecraft.client.MinecraftClient
import net.minecraft.client.render.RenderLayer
import net.minecraft.component.type.ProfileComponent
import net.minecraft.util.Identifier
import moe.nea.firmament.annotations.Subscribe
import moe.nea.firmament.events.CustomItemModelEvent
import moe.nea.firmament.events.FinalizeResourceManagerEvent
import moe.nea.firmament.events.TickEvent
import moe.nea.firmament.features.FirmamentFeature
import moe.nea.firmament.features.debug.PowerUserTools
import moe.nea.firmament.gui.config.ManagedConfig
import moe.nea.firmament.util.collections.WeakCache
import moe.nea.firmament.util.mc.decodeProfileTextureProperty
import moe.nea.firmament.util.skyBlockId
object CustomSkyBlockTextures : FirmamentFeature {
override val identifier: String
get() = "custom-skyblock-textures"
object TConfig : ManagedConfig(identifier, Category.INTEGRATIONS) { // TODO: should this be its own thing?
val enabled by toggle("enabled") { true }
val skullsEnabled by toggle("skulls-enabled") { true }
val cacheForever by toggle("cache-forever") { true }
val cacheDuration by integer("cache-duration", 0, 100) { 1 }
val enableModelOverrides by toggle("model-overrides") { true }
val enableArmorOverrides by toggle("armor-overrides") { true }
val enableBlockOverrides by toggle("block-overrides") { true }
val enableLegacyCIT by toggle("legacy-cit") { true }
val allowRecoloringUiText by toggle("recolor-text") { true }
}
override val config: ManagedConfig
get() = TConfig
val allItemCaches by lazy {
listOf(
skullTextureCache.cache,
CustomGlobalArmorOverrides.overrideCache.cache
)
}
init {
PowerUserTools.getSkullId = ::getSkullTexture
}
fun clearAllCaches() {
allItemCaches.forEach(WeakCache<*, *, *>::clear)
}
@Subscribe
fun onTick(it: TickEvent) {
if (TConfig.cacheForever) return
if (TConfig.cacheDuration < 1 || it.tickCount % TConfig.cacheDuration == 0) {
clearAllCaches()
}
}
@Subscribe
fun onStart(event: FinalizeResourceManagerEvent) {
event.registerOnApply("Clear firmament CIT caches") {
clearAllCaches()
}
}
@Subscribe
fun onCustomModelId(it: CustomItemModelEvent) {
if (!TConfig.enabled) return
val id = it.itemStack.skyBlockId ?: return
it.overrideIfExists(Identifier.of("firmskyblock", id.identifier.path))
}
private val skullTextureCache =
WeakCache.memoize<ProfileComponent, Optional<Identifier>>("SkullTextureCache") { component ->
val id = getSkullTexture(component) ?: return@memoize Optional.empty()
if (!MinecraftClient.getInstance().resourceManager.getResource(id).isPresent) {
return@memoize Optional.empty()
}
return@memoize Optional.of(id)
}
private val mcUrlRegex = "https?://textures.minecraft.net/texture/([a-fA-F0-9]+)".toRegex()
fun getSkullId(textureProperty: Property): String? {
val texture = decodeProfileTextureProperty(textureProperty) ?: return null
val textureUrl =
texture.textures[MinecraftProfileTexture.Type.SKIN]?.url ?: return null
val mcUrlData = mcUrlRegex.matchEntire(textureUrl) ?: return null
return mcUrlData.groupValues[1]
}
fun getSkullTexture(profile: ProfileComponent): Identifier? {
val id = getSkullId(profile.properties["textures"].firstOrNull() ?: return null) ?: return null
return Identifier.of("firmskyblock", "textures/placedskull/$id.png")
}
fun modifySkullTexture(
type: SkullBlock.SkullType?,
component: ProfileComponent?,
cir: CallbackInfoReturnable<RenderLayer>
) {
if (type != SkullBlock.Type.PLAYER) return
if (!TConfig.skullsEnabled) return
if (component == null) return
val n = skullTextureCache.invoke(component).getOrNull() ?: return
cir.returnValue = RenderLayer.getEntityTranslucent(n)
}
}

View File

@@ -0,0 +1,23 @@
package moe.nea.firmament.features.texturepack
import com.google.gson.JsonObject
import kotlinx.serialization.KSerializer
import kotlinx.serialization.descriptors.SerialDescriptor
import kotlinx.serialization.encoding.Decoder
import kotlinx.serialization.encoding.Encoder
import moe.nea.firmament.features.texturepack.predicates.AndPredicate
object FirmamentRootPredicateSerializer : KSerializer<FirmamentModelPredicate> {
val delegateSerializer = kotlinx.serialization.json.JsonObject.serializer()
override val descriptor: SerialDescriptor
get() = SerialDescriptor("FirmamentModelRootPredicate", delegateSerializer.descriptor)
override fun deserialize(decoder: Decoder): FirmamentModelPredicate {
val json = decoder.decodeSerializableValue(delegateSerializer).intoGson() as JsonObject
return AndPredicate(CustomModelOverrideParser.parsePredicates(json).toTypedArray())
}
override fun serialize(encoder: Encoder, value: FirmamentModelPredicate) {
TODO("Cannot serialize firmament predicates")
}
}

View File

@@ -0,0 +1,85 @@
package moe.nea.firmament.features.texturepack
import com.mojang.serialization.Codec
import com.mojang.serialization.MapCodec
import com.mojang.serialization.codecs.RecordCodecBuilder
import net.minecraft.client.item.ItemModelManager
import net.minecraft.client.render.item.ItemRenderState
import net.minecraft.client.render.item.model.ItemModel
import net.minecraft.client.render.item.model.ItemModelTypes
import net.minecraft.client.render.model.ResolvableModel
import net.minecraft.client.world.ClientWorld
import net.minecraft.entity.LivingEntity
import net.minecraft.item.ItemStack
import net.minecraft.item.ModelTransformationMode
class PredicateModel {
data class Baked(
val fallback: ItemModel,
val overrides: List<Override>
) : ItemModel {
data class Override(
val model: ItemModel,
val predicate: FirmamentModelPredicate,
)
override fun update(
state: ItemRenderState,
stack: ItemStack,
resolver: ItemModelManager,
transformationMode: ModelTransformationMode,
world: ClientWorld?,
user: LivingEntity?,
seed: Int
) {
val model =
overrides
.find { it.predicate.test(stack) }
?.model
?: fallback
model.update(state, stack, resolver, transformationMode, world, user, seed)
}
}
data class Unbaked(
val fallback: ItemModel.Unbaked,
val overrides: List<Override>,
) : ItemModel.Unbaked {
companion object {
val OVERRIDE_CODEC: Codec<Override> = RecordCodecBuilder.create {
it.group(
ItemModelTypes.CODEC.fieldOf("model").forGetter(Override::model),
CustomModelOverrideParser.LEGACY_CODEC.fieldOf("predicate").forGetter(Override::predicate),
).apply(it, Unbaked::Override)
}
val CODEC: MapCodec<Unbaked> =
RecordCodecBuilder.mapCodec {
it.group(
ItemModelTypes.CODEC.fieldOf("fallback").forGetter(Unbaked::fallback),
OVERRIDE_CODEC.listOf().fieldOf("overrides").forGetter(Unbaked::overrides),
).apply(it, ::Unbaked)
}
}
data class Override(
val model: ItemModel.Unbaked,
val predicate: FirmamentModelPredicate,
)
override fun resolve(resolver: ResolvableModel.Resolver) {
fallback.resolve(resolver)
overrides.forEach { it.model.resolve(resolver) }
}
override fun getCodec(): MapCodec<out Unbaked> {
return CODEC
}
override fun bake(context: ItemModel.BakeContext): ItemModel {
return Baked(
fallback.bake(context),
overrides.map { Baked.Override(it.model.bake(context), it.predicate) }
)
}
}
}

View File

@@ -70,7 +70,7 @@ interface StringMatcher {
}
override fun serialize(encoder: Encoder, value: StringMatcher) {
encoder.encodeSerializableValue(delegateSerializer, Companion.serialize(value).intoKotlinJson())
encoder.encodeSerializableValue(delegateSerializer, serialize(value).intoKotlinJson())
}
}

View File

@@ -43,7 +43,7 @@ data class TintOverrides(
val override = (value as? JsonPrimitive)
?.takeIf(JsonPrimitive::isNumber)
?.asInt
?.let(::Fixed)
?.let(TintOverrides::Fixed)
if (override == null) {
ErrorUtil.softError("Invalid tint override for a layer: $value")
continue

View File

@@ -0,0 +1,11 @@
package moe.nea.firmament.mixins.custommodels;
import net.minecraft.client.item.ItemModelManager;
import org.spongepowered.asm.mixin.Mixin;
@Mixin(ItemModelManager.class)
public class ApplyHeadModelInItemRenderer {
// TODO: replace head_model with a condition model (if possible, automatically)
// TODO: ItemAsset.CODEC should upgrade partials
}

View File

@@ -1,12 +1,13 @@
package moe.nea.firmament.mixins;
package moe.nea.firmament.mixins.custommodels;
import moe.nea.firmament.features.texturepack.CustomSkyBlockTextures;
import net.minecraft.block.SkullBlock;
import net.minecraft.client.render.RenderLayer;
import net.minecraft.client.render.block.entity.SkullBlockEntityRenderer;
import net.minecraft.component.type.ProfileComponent;
import net.minecraft.util.Identifier;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
@@ -14,8 +15,12 @@ import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
@Mixin(SkullBlockEntityRenderer.class)
public class CustomSkullTexturePatch {
@Inject(method = "getRenderLayer", at = @At("HEAD"), cancellable = true)
private static void onGetRenderLayer(SkullBlock.SkullType type, ProfileComponent profile, CallbackInfoReturnable<RenderLayer> cir) {
@Inject(
method = "getRenderLayer(Lnet/minecraft/block/SkullBlock$SkullType;Lnet/minecraft/component/type/ProfileComponent;Lnet/minecraft/util/Identifier;)Lnet/minecraft/client/render/RenderLayer;",
at = @At("HEAD"),
cancellable = true
)
private static void onGetRenderLayer(SkullBlock.SkullType type, ProfileComponent profile, Identifier texture, CallbackInfoReturnable<RenderLayer> cir) {
CustomSkyBlockTextures.INSTANCE.modifySkullTexture(type, profile, cir);
}
}

View File

@@ -1,29 +1,30 @@
package moe.nea.firmament.mixins.custommodels;
import com.llamalad7.mixinextras.injector.ModifyExpressionValue;
import com.llamalad7.mixinextras.injector.wrapoperation.Operation;
import com.llamalad7.mixinextras.injector.wrapoperation.WrapOperation;
import com.llamalad7.mixinextras.sugar.Local;
import moe.nea.firmament.features.texturepack.CustomGlobalArmorOverrides;
import net.minecraft.client.render.entity.feature.ArmorFeatureRenderer;
import net.minecraft.component.ComponentType;
import net.minecraft.component.type.EquippableComponent;
import net.minecraft.entity.EquipmentSlot;
import net.minecraft.item.ItemStack;
import net.minecraft.util.Identifier;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.injection.At;
import java.util.Optional;
@Mixin(ArmorFeatureRenderer.class)
public class PatchArmorTexture {
@WrapOperation(
@ModifyExpressionValue(
method = "renderArmor",
at = @At(value = "INVOKE", target = "Lnet/minecraft/component/type/EquippableComponent;model()Ljava/util/Optional;"))
private Optional<Identifier> overrideLayers(
EquippableComponent instance, Operation<Optional<Identifier>> original, @Local(argsOnly = true) ItemStack itemStack
at = @At(
value = "INVOKE",
target = "Lnet/minecraft/item/ItemStack;get(Lnet/minecraft/component/ComponentType;)Ljava/lang/Object;"))
private Object overrideLayers(
Object original, @Local(argsOnly = true) ItemStack itemStack, @Local(argsOnly = true) EquipmentSlot slot
) {
// TODO: check that all armour items are naturally equippable and have the equppable component. otherwise our call here will not be reached.
var overrides = CustomGlobalArmorOverrides.overrideArmor(itemStack);
return overrides.or(() -> original.call(instance));
var overrides = CustomGlobalArmorOverrides.overrideArmor(itemStack, slot);
return overrides.orElse((EquippableComponent) original);
}
}

View File

@@ -0,0 +1,23 @@
package moe.nea.firmament.mixins.custommodels;
import com.llamalad7.mixinextras.injector.wrapoperation.Operation;
import com.llamalad7.mixinextras.injector.wrapoperation.WrapOperation;
import moe.nea.firmament.features.texturepack.CustomGlobalArmorOverrides;
import net.minecraft.client.render.entity.equipment.EquipmentModel;
import net.minecraft.client.render.entity.equipment.EquipmentModelLoader;
import net.minecraft.client.render.entity.equipment.EquipmentRenderer;
import net.minecraft.item.equipment.EquipmentAsset;
import net.minecraft.registry.RegistryKey;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.injection.At;
// TODO: auto import legacy models, maybe!!! in a later patch tho
@Mixin(EquipmentRenderer.class)
public class PatchLegacyArmorLayerSupport {
@WrapOperation(method = "render(Lnet/minecraft/client/render/entity/equipment/EquipmentModel$LayerType;Lnet/minecraft/registry/RegistryKey;Lnet/minecraft/client/model/Model;Lnet/minecraft/item/ItemStack;Lnet/minecraft/client/util/math/MatrixStack;Lnet/minecraft/client/render/VertexConsumerProvider;ILnet/minecraft/util/Identifier;)V", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/render/entity/equipment/EquipmentModelLoader;get(Lnet/minecraft/registry/RegistryKey;)Lnet/minecraft/client/render/entity/equipment/EquipmentModel;"))
private EquipmentModel patchModelLayers(EquipmentModelLoader instance, RegistryKey<EquipmentAsset> assetKey, Operation<EquipmentModel> original) {
var modelOverride = CustomGlobalArmorOverrides.overrideArmorLayer(assetKey.getValue());
if (modelOverride != null) return modelOverride;
return original.call(instance, assetKey);
}
}

View File

@@ -1,8 +1,10 @@
package moe.nea.firmament.mixins.custommodels;
import com.llamalad7.mixinextras.sugar.Local;
import moe.nea.firmament.events.BakeExtraModelsEvent;
import net.minecraft.client.item.ItemAssetsLoader;
import net.minecraft.client.render.model.BakedModelManager;
import net.minecraft.client.render.model.BlockStatesLoader;
import net.minecraft.client.render.model.ItemModel;
import net.minecraft.client.render.model.ReferencedModelsCollector;
import net.minecraft.client.render.model.UnbakedModel;
import net.minecraft.client.util.ModelIdentifier;
@@ -13,24 +15,17 @@ import org.spongepowered.asm.mixin.Shadow;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
import java.util.Map;
@Mixin(ReferencedModelsCollector.class)
@Mixin(BakedModelManager.class)
public abstract class ReferenceCustomModelsPatch {
@Shadow
protected abstract void addTopLevelModel(ModelIdentifier modelId, UnbakedModel model);
@Shadow
@Final
private Map<Identifier, UnbakedModel> inputs;
@Inject(method = "addBlockStates", at = @At("RETURN"))
private void addFirmamentReferencedModels(
BlockStatesLoader.BlockStateDefinition definition, CallbackInfo ci
) {
BakeExtraModelsEvent.Companion.publish(new BakeExtraModelsEvent(
(modelIdentifier, identifier) -> addTopLevelModel(modelIdentifier, new ItemModel(identifier))));
@Inject(method = "collect", at = @At("RETURN"))
private static void addFirmamentReferencedModels(
UnbakedModel missingModel, Map<Identifier, UnbakedModel> models, BlockStatesLoader.BlockStateDefinition blockStates, ItemAssetsLoader.Result itemAssets, CallbackInfoReturnable<ReferencedModelsCollector> cir,
@Local ReferencedModelsCollector collector) {
// TODO: Insert fake models based on firmskyblock models for a smoother transition
}
}

View File

@@ -0,0 +1,24 @@
package moe.nea.firmament.mixins.custommodels;
import com.llamalad7.mixinextras.injector.wrapoperation.Operation;
import com.llamalad7.mixinextras.injector.wrapoperation.WrapOperation;
import moe.nea.firmament.events.CustomItemModelEvent;
import net.minecraft.client.item.ItemModelManager;
import net.minecraft.component.ComponentType;
import net.minecraft.item.ItemStack;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.injection.At;
@Mixin(ItemModelManager.class)
public class ReplaceItemModelPatch {
@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",
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) {
var override = CustomItemModelEvent.getModelIdentifier(instance);
if (override != null)
return override;
return original.call(instance, componentType);
}
}

View File

@@ -1,4 +1,4 @@
package moe.nea.firmament.mixins;
package moe.nea.firmament.mixins.custommodels;
import com.llamalad7.mixinextras.injector.wrapoperation.Operation;

View File

@@ -44,6 +44,7 @@ class SubscribeAnnotationProcessor(
appendLine()
appendLine("import moe.nea.firmament.events.subscription.*")
appendLine()
appendLine("@Suppress()")
appendLine("class $generatedFileName : SubscriptionList {")
appendLine(" override fun provideSubscriptions(addSubscription: (Subscription<*>) -> Unit) {")
for (subscription in subscriptions) {