From 8b410fbdf2cffb3ceaa51bbea150f5165848bc37 Mon Sep 17 00:00:00 2001 From: Linnea Gräf Date: Fri, 1 Nov 2024 23:00:53 +0100 Subject: Add tint override to texture packs --- docs/Texture Pack Format.md | 35 +++ gradle/libs.versions.toml | 2 +- .../java/moe/nea/firmament/init/EarlyRiser.java | 1 + .../nea/firmament/init/ItemColorsSodiumRiser.java | 64 +++++ .../custommodels/BakedModelDataHolderBasic.java | 36 ++- .../custommodels/BakedModelDataHolderBuiltin.java | 37 ++- .../mixins/custommodels/ItemColorRemovalPatch.java | 39 +++ .../ItemModelGeneratorJsonUnbakedModelCopy.java | 7 +- .../custommodels/ItemRendererTintContextPatch.java | 33 +++ .../custommodels/JsonUnbakedModelDataHolder.java | 107 +++++--- .../PatchJsonUnbakedModelDeserializer.java | 24 +- .../kotlin/features/texturepack/AlwaysPredicate.kt | 17 -- .../kotlin/features/texturepack/AndPredicate.kt | 25 -- .../kotlin/features/texturepack/BakedModelExtra.kt | 4 +- .../texturepack/CustomModelOverrideParser.kt | 8 + .../features/texturepack/DisplayNamePredicate.kt | 22 -- .../texturepack/ExtraAttributesPredicate.kt | 268 -------------------- .../kotlin/features/texturepack/ItemPredicate.kt | 32 --- .../texturepack/JsonUnbakedModelFirmExtra.kt | 4 + .../kotlin/features/texturepack/LorePredicate.kt | 19 -- .../features/texturepack/ModelOverrideFilterSet.kt | 19 -- .../kotlin/features/texturepack/NotPredicate.kt | 18 -- .../kotlin/features/texturepack/NumberMatcher.kt | 124 ---------- .../kotlin/features/texturepack/OrPredicate.kt | 26 -- .../kotlin/features/texturepack/PetPredicate.kt | 66 ----- .../kotlin/features/texturepack/TintOverrides.kt | 75 ++++++ .../texturepack/predicates/AlwaysPredicate.kt | 19 ++ .../texturepack/predicates/AndPredicate.kt | 28 +++ .../texturepack/predicates/DisplayNamePredicate.kt | 22 ++ .../predicates/ExtraAttributesPredicate.kt | 271 +++++++++++++++++++++ .../texturepack/predicates/ItemPredicate.kt | 34 +++ .../texturepack/predicates/LorePredicate.kt | 22 ++ .../texturepack/predicates/NotPredicate.kt | 21 ++ .../texturepack/predicates/NumberMatcher.kt | 124 ++++++++++ .../features/texturepack/predicates/OrPredicate.kt | 29 +++ .../texturepack/predicates/PetPredicate.kt | 66 +++++ src/main/kotlin/repo/RepoManager.kt | 7 +- src/main/kotlin/util/ErrorUtil.kt | 14 ++ src/main/kotlin/util/filter/IteratorFilterSet.kt | 33 --- 39 files changed, 1056 insertions(+), 746 deletions(-) create mode 100644 src/main/java/moe/nea/firmament/init/ItemColorsSodiumRiser.java create mode 100644 src/main/java/moe/nea/firmament/mixins/custommodels/ItemColorRemovalPatch.java create mode 100644 src/main/java/moe/nea/firmament/mixins/custommodels/ItemRendererTintContextPatch.java delete mode 100644 src/main/kotlin/features/texturepack/AlwaysPredicate.kt delete mode 100644 src/main/kotlin/features/texturepack/AndPredicate.kt delete mode 100644 src/main/kotlin/features/texturepack/DisplayNamePredicate.kt delete mode 100644 src/main/kotlin/features/texturepack/ExtraAttributesPredicate.kt delete mode 100644 src/main/kotlin/features/texturepack/ItemPredicate.kt delete mode 100644 src/main/kotlin/features/texturepack/LorePredicate.kt delete mode 100644 src/main/kotlin/features/texturepack/ModelOverrideFilterSet.kt delete mode 100644 src/main/kotlin/features/texturepack/NotPredicate.kt delete mode 100644 src/main/kotlin/features/texturepack/NumberMatcher.kt delete mode 100644 src/main/kotlin/features/texturepack/OrPredicate.kt delete mode 100644 src/main/kotlin/features/texturepack/PetPredicate.kt create mode 100644 src/main/kotlin/features/texturepack/TintOverrides.kt create mode 100644 src/main/kotlin/features/texturepack/predicates/AlwaysPredicate.kt create mode 100644 src/main/kotlin/features/texturepack/predicates/AndPredicate.kt create mode 100644 src/main/kotlin/features/texturepack/predicates/DisplayNamePredicate.kt create mode 100644 src/main/kotlin/features/texturepack/predicates/ExtraAttributesPredicate.kt create mode 100644 src/main/kotlin/features/texturepack/predicates/ItemPredicate.kt create mode 100644 src/main/kotlin/features/texturepack/predicates/LorePredicate.kt create mode 100644 src/main/kotlin/features/texturepack/predicates/NotPredicate.kt create mode 100644 src/main/kotlin/features/texturepack/predicates/NumberMatcher.kt create mode 100644 src/main/kotlin/features/texturepack/predicates/OrPredicate.kt create mode 100644 src/main/kotlin/features/texturepack/predicates/PetPredicate.kt delete mode 100644 src/main/kotlin/util/filter/IteratorFilterSet.kt diff --git a/docs/Texture Pack Format.md b/docs/Texture Pack Format.md index 23dd7a1..b52c038 100644 --- a/docs/Texture Pack Format.md +++ b/docs/Texture Pack Format.md @@ -40,6 +40,41 @@ head model. } ``` +## Tint Overrides + +Some items get naturally tinted by Minecraft's rendering. Examples include leather armour, spawn eggs, potions and more. +If you want to avoid your textures getting tinted, one thing you can do is use a higher texture layer: + +```json +{ + "parent": "minecraft:item/generated", + "textures": { + // Notice the layer1 instead of layer0 here + "layer1": "firmskyblock:item/regular_texture" + } +} +``` + +Some items, however, tint *all* layers. For those items you can instead specify a tint override: + +```json +{ + "parent": "minecraft:item/generated", + "textures": { + "layer0": "firmskyblock:item/regular_texture" + }, + "firmament:tint_overrides": { + "0": -1 + } +} +``` + +This forces layer 0 to be tinted with the color `-1` (pure white, aka no tint). This property is inherited, so if you +attach it to one of your root models that you `"parent"` other models to, all those models will have their tints +overridden. When the property is inherited, only layers specified in the child actually overwrite the parent layers. +You can use `"0": null` to remove the tint override in a child, which will cause a fallback to the vanilla tinting +behaviour. + ## Predicates Firmament adds the ability for more complex [item model predicates](https://minecraft.wiki/w/Tutorials/Models#Item_predicates). diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index d3842d4..c11d939 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -129,7 +129,7 @@ runtime_required = [ runtime_optional = [ "devauth", # "freecammod", - "sodium", +# "sodium", # "qolify", "ncr", "citresewn", diff --git a/src/main/java/moe/nea/firmament/init/EarlyRiser.java b/src/main/java/moe/nea/firmament/init/EarlyRiser.java index 5eab563..9734e94 100644 --- a/src/main/java/moe/nea/firmament/init/EarlyRiser.java +++ b/src/main/java/moe/nea/firmament/init/EarlyRiser.java @@ -7,5 +7,6 @@ public class EarlyRiser implements Runnable { new ClientPlayerRiser().addTinkerers(); new HandledScreenRiser().addTinkerers(); new SectionBuilderRiser().addTinkerers(); + new ItemColorsSodiumRiser().addTinkerers(); } } diff --git a/src/main/java/moe/nea/firmament/init/ItemColorsSodiumRiser.java b/src/main/java/moe/nea/firmament/init/ItemColorsSodiumRiser.java new file mode 100644 index 0000000..80ee9aa --- /dev/null +++ b/src/main/java/moe/nea/firmament/init/ItemColorsSodiumRiser.java @@ -0,0 +1,64 @@ +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; + } +} diff --git a/src/main/java/moe/nea/firmament/mixins/custommodels/BakedModelDataHolderBasic.java b/src/main/java/moe/nea/firmament/mixins/custommodels/BakedModelDataHolderBasic.java index 7bf3732..3ed2177 100644 --- a/src/main/java/moe/nea/firmament/mixins/custommodels/BakedModelDataHolderBasic.java +++ b/src/main/java/moe/nea/firmament/mixins/custommodels/BakedModelDataHolderBasic.java @@ -2,6 +2,7 @@ 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; @@ -11,18 +12,31 @@ import org.spongepowered.asm.mixin.Unique; @Mixin(BasicBakedModel.class) public class BakedModelDataHolderBasic implements BakedModelExtra { - @Unique - private BakedModel headModel; + @Unique + private BakedModel headModel; + @Unique + @Nullable + private TintOverrides tintOverrides; - @Nullable - @Override - public BakedModel getHeadModel_firmament() { - return headModel; - } + @Nullable + @Override + public BakedModel getHeadModel_firmament() { + return headModel; + } - @Override - public void setHeadModel_firmament(@Nullable BakedModel headModel) { - this.headModel = 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; + } } diff --git a/src/main/java/moe/nea/firmament/mixins/custommodels/BakedModelDataHolderBuiltin.java b/src/main/java/moe/nea/firmament/mixins/custommodels/BakedModelDataHolderBuiltin.java index 76300a1..87aecb1 100644 --- a/src/main/java/moe/nea/firmament/mixins/custommodels/BakedModelDataHolderBuiltin.java +++ b/src/main/java/moe/nea/firmament/mixins/custommodels/BakedModelDataHolderBuiltin.java @@ -2,6 +2,7 @@ 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; @@ -11,18 +12,32 @@ import org.spongepowered.asm.mixin.Unique; @Mixin(BuiltinBakedModel.class) public class BakedModelDataHolderBuiltin implements BakedModelExtra { - @Unique - private BakedModel headModel; + @Unique + @Nullable + private BakedModel headModel; + @Unique + @Nullable + private TintOverrides tintOverrides; - @Nullable - @Override - public BakedModel getHeadModel_firmament() { - return headModel; - } + @Override + public @Nullable TintOverrides getTintOverrides_firmament() { + return tintOverrides; + } - @Override - public void setHeadModel_firmament(@Nullable BakedModel headModel) { - this.headModel = headModel; - } + @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; + } } diff --git a/src/main/java/moe/nea/firmament/mixins/custommodels/ItemColorRemovalPatch.java b/src/main/java/moe/nea/firmament/mixins/custommodels/ItemColorRemovalPatch.java new file mode 100644 index 0000000..8c76c60 --- /dev/null +++ b/src/main/java/moe/nea/firmament/mixins/custommodels/ItemColorRemovalPatch.java @@ -0,0 +1,39 @@ +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 cir) { + var tintOverrides = TintOverrides.Companion.getCurrentOverrides(); + var override = tintOverrides.getOverride(tintIndex); + if (override != null) + cir.setReturnValue(override); + } +} diff --git a/src/main/java/moe/nea/firmament/mixins/custommodels/ItemModelGeneratorJsonUnbakedModelCopy.java b/src/main/java/moe/nea/firmament/mixins/custommodels/ItemModelGeneratorJsonUnbakedModelCopy.java index c897441..89d0411 100644 --- a/src/main/java/moe/nea/firmament/mixins/custommodels/ItemModelGeneratorJsonUnbakedModelCopy.java +++ b/src/main/java/moe/nea/firmament/mixins/custommodels/ItemModelGeneratorJsonUnbakedModelCopy.java @@ -12,8 +12,11 @@ import org.spongepowered.asm.mixin.injection.At; @Mixin(ItemModelGenerator.class) public class ItemModelGeneratorJsonUnbakedModelCopy { @ModifyReturnValue(method = "create", at = @At("RETURN")) - private JsonUnbakedModel copyHeadModel(JsonUnbakedModel original, @Local(argsOnly = true) JsonUnbakedModel oldModel) { - ((JsonUnbakedModelFirmExtra) original).setHeadModel_firmament(((JsonUnbakedModelFirmExtra) oldModel).getHeadModel_firmament()); + 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; } } diff --git a/src/main/java/moe/nea/firmament/mixins/custommodels/ItemRendererTintContextPatch.java b/src/main/java/moe/nea/firmament/mixins/custommodels/ItemRendererTintContextPatch.java new file mode 100644 index 0000000..5ed0fbe --- /dev/null +++ b/src/main/java/moe/nea/firmament/mixins/custommodels/ItemRendererTintContextPatch.java @@ -0,0 +1,33 @@ +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.render.model.json.ModelTransformationMode; +import net.minecraft.client.util.math.MatrixStack; +import net.minecraft.item.ItemStack; +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 ItemRendererTintContextPatch { + @Inject(method = "renderItem(Lnet/minecraft/item/ItemStack;Lnet/minecraft/client/render/model/json/ModelTransformationMode;ZLnet/minecraft/client/util/math/MatrixStack;Lnet/minecraft/client/render/VertexConsumerProvider;IILnet/minecraft/client/render/model/BakedModel;)V", + at = @At(value = "INVOKE", target = "Lnet/minecraft/client/render/model/BakedModel;getTransformation()Lnet/minecraft/client/render/model/json/ModelTransformation;"), allow = 1) + private void onStartRendering(ItemStack stack, ModelTransformationMode renderMode, boolean leftHanded, MatrixStack matrices, VertexConsumerProvider vertexConsumers, int light, int overlay, BakedModel model, CallbackInfo ci) { + if (model instanceof BakedModelExtra extra) { + TintOverrides.Companion.enter(extra.getTintOverrides_firmament()); + } + } + + @Inject(method = "renderItem(Lnet/minecraft/item/ItemStack;Lnet/minecraft/client/render/model/json/ModelTransformationMode;ZLnet/minecraft/client/util/math/MatrixStack;Lnet/minecraft/client/render/VertexConsumerProvider;IILnet/minecraft/client/render/model/BakedModel;)V", + at = @At(value = "INVOKE", target = "Lnet/minecraft/client/util/math/MatrixStack;pop()V"), allow = 1) + private void onEndRendering(ItemStack stack, ModelTransformationMode renderMode, boolean leftHanded, MatrixStack matrices, VertexConsumerProvider vertexConsumers, int light, int overlay, BakedModel model, CallbackInfo ci) { + if (model instanceof BakedModelExtra extra) { + TintOverrides.Companion.exit(extra.getTintOverrides_firmament()); + } + } +} diff --git a/src/main/java/moe/nea/firmament/mixins/custommodels/JsonUnbakedModelDataHolder.java b/src/main/java/moe/nea/firmament/mixins/custommodels/JsonUnbakedModelDataHolder.java index 5fe3dec..20c69e2 100644 --- a/src/main/java/moe/nea/firmament/mixins/custommodels/JsonUnbakedModelDataHolder.java +++ b/src/main/java/moe/nea/firmament/mixins/custommodels/JsonUnbakedModelDataHolder.java @@ -1,4 +1,3 @@ - package moe.nea.firmament.mixins.custommodels; import com.google.gson.annotations.SerializedName; @@ -6,12 +5,14 @@ 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 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; @@ -23,46 +24,74 @@ import java.util.Objects; @Mixin(JsonUnbakedModel.class) public class JsonUnbakedModelDataHolder implements JsonUnbakedModelFirmExtra { - @Shadow - @Nullable - protected JsonUnbakedModel parent; - @Unique - @Nullable - public Identifier headModel; + @Shadow + @Nullable + protected JsonUnbakedModel parent; + @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 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(); - } + @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(); + } - @ModifyReturnValue(method = "getModelDependencies", at = @At("RETURN")) - private Collection addDependencies(Collection original) { - var headModel = getHeadModel_firmament(); - if (headModel != null) { - original.add(headModel); - } - return original; - } + @ModifyReturnValue(method = "getModelDependencies", at = @At("RETURN")) + private Collection addDependencies(Collection original) { + var headModel = getHeadModel_firmament(); + if (headModel != null) { + original.add(headModel); + } + return original; + } - @ModifyReturnValue( - method = "bake(Lnet/minecraft/client/render/model/Baker;Lnet/minecraft/client/render/model/json/JsonUnbakedModel;Ljava/util/function/Function;Lnet/minecraft/client/render/model/ModelBakeSettings;Z)Lnet/minecraft/client/render/model/BakedModel;", - at = @At(value = "RETURN")) - private BakedModel bakeExtraInfo(BakedModel original, @Local(argsOnly = true) Baker baker) { - var headModel = getHeadModel_firmament(); - if (headModel != null && original instanceof BakedModelExtra extra) { - UnbakedModel unbakedModel = baker.getOrLoadModel(headModel); - extra.setHeadModel_firmament( - Objects.equals(unbakedModel, parent) - ? null - : baker.bake(headModel, ModelRotation.X0_Y0)); - } - return original; - } + @ModifyReturnValue( + method = "bake(Lnet/minecraft/client/render/model/Baker;Lnet/minecraft/client/render/model/json/JsonUnbakedModel;Ljava/util/function/Function;Lnet/minecraft/client/render/model/ModelBakeSettings;Z)Lnet/minecraft/client/render/model/BakedModel;", + at = @At(value = "RETURN")) + private BakedModel bakeExtraInfo(BakedModel original, @Local(argsOnly = true) Baker baker) { + if (original instanceof BakedModelExtra extra) { + var headModel = getHeadModel_firmament(); + if (headModel != null) { + UnbakedModel unbakedModel = baker.getOrLoadModel(headModel); + extra.setHeadModel_firmament( + Objects.equals(unbakedModel, parent) + ? null + : baker.bake(headModel, ModelRotation.X0_Y0)); + } + if (getTintOverrides_firmament().hasOverrides()) + extra.setTintOverrides_firmament(getTintOverrides_firmament()); + } + return original; + } } diff --git a/src/main/java/moe/nea/firmament/mixins/custommodels/PatchJsonUnbakedModelDeserializer.java b/src/main/java/moe/nea/firmament/mixins/custommodels/PatchJsonUnbakedModelDeserializer.java index cd6fb17..d6c25b5 100644 --- a/src/main/java/moe/nea/firmament/mixins/custommodels/PatchJsonUnbakedModelDeserializer.java +++ b/src/main/java/moe/nea/firmament/mixins/custommodels/PatchJsonUnbakedModelDeserializer.java @@ -6,6 +6,7 @@ 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; @@ -13,13 +14,18 @@ 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"); - if (headModel instanceof JsonPrimitive prim && prim.isString()) { - ((JsonUnbakedModelFirmExtra) original).setHeadModel_firmament(Identifier.of(prim.getAsString())); - } - return original; - } + @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; + } } diff --git a/src/main/kotlin/features/texturepack/AlwaysPredicate.kt b/src/main/kotlin/features/texturepack/AlwaysPredicate.kt deleted file mode 100644 index 4dd28df..0000000 --- a/src/main/kotlin/features/texturepack/AlwaysPredicate.kt +++ /dev/null @@ -1,17 +0,0 @@ - -package moe.nea.firmament.features.texturepack - -import com.google.gson.JsonElement -import net.minecraft.item.ItemStack - -object AlwaysPredicate : FirmamentModelPredicate { - override fun test(stack: ItemStack): Boolean { - return true - } - - object Parser : FirmamentModelPredicateParser { - override fun parse(jsonElement: JsonElement): FirmamentModelPredicate { - return AlwaysPredicate - } - } -} diff --git a/src/main/kotlin/features/texturepack/AndPredicate.kt b/src/main/kotlin/features/texturepack/AndPredicate.kt deleted file mode 100644 index dc8e852..0000000 --- a/src/main/kotlin/features/texturepack/AndPredicate.kt +++ /dev/null @@ -1,25 +0,0 @@ -package moe.nea.firmament.features.texturepack - -import com.google.gson.JsonArray -import com.google.gson.JsonElement -import com.google.gson.JsonObject -import net.minecraft.item.ItemStack - -class AndPredicate(val children: Array) : FirmamentModelPredicate { - override fun test(stack: ItemStack): Boolean { - return children.all { it.test(stack) } - } - - object Parser : FirmamentModelPredicateParser { - override fun parse(jsonElement: JsonElement): FirmamentModelPredicate { - val children = - (jsonElement as JsonArray) - .flatMap { - CustomModelOverrideParser.parsePredicates(it as JsonObject) - } - .toTypedArray() - return AndPredicate(children) - } - - } -} diff --git a/src/main/kotlin/features/texturepack/BakedModelExtra.kt b/src/main/kotlin/features/texturepack/BakedModelExtra.kt index ae1f6d5..32f419a 100644 --- a/src/main/kotlin/features/texturepack/BakedModelExtra.kt +++ b/src/main/kotlin/features/texturepack/BakedModelExtra.kt @@ -4,6 +4,8 @@ package moe.nea.firmament.features.texturepack import net.minecraft.client.render.model.BakedModel interface BakedModelExtra { - fun getHeadModel_firmament(): BakedModel? + var tintOverrides_firmament: TintOverrides? + + fun getHeadModel_firmament(): BakedModel? fun setHeadModel_firmament(headModel: BakedModel?) } diff --git a/src/main/kotlin/features/texturepack/CustomModelOverrideParser.kt b/src/main/kotlin/features/texturepack/CustomModelOverrideParser.kt index a4e7c02..c5fc20b 100644 --- a/src/main/kotlin/features/texturepack/CustomModelOverrideParser.kt +++ b/src/main/kotlin/features/texturepack/CustomModelOverrideParser.kt @@ -6,6 +6,14 @@ 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 diff --git a/src/main/kotlin/features/texturepack/DisplayNamePredicate.kt b/src/main/kotlin/features/texturepack/DisplayNamePredicate.kt deleted file mode 100644 index 100aaf4..0000000 --- a/src/main/kotlin/features/texturepack/DisplayNamePredicate.kt +++ /dev/null @@ -1,22 +0,0 @@ - -package moe.nea.firmament.features.texturepack - -import com.google.gson.JsonElement -import net.minecraft.item.ItemStack -import net.minecraft.nbt.NbtElement -import net.minecraft.nbt.NbtString -import moe.nea.firmament.util.mc.displayNameAccordingToNbt -import moe.nea.firmament.util.mc.loreAccordingToNbt - -data class DisplayNamePredicate(val stringMatcher: StringMatcher) : FirmamentModelPredicate { - override fun test(stack: ItemStack): Boolean { - val display = stack.displayNameAccordingToNbt - return stringMatcher.matches(display) - } - - object Parser : FirmamentModelPredicateParser { - override fun parse(jsonElement: JsonElement): FirmamentModelPredicate { - return DisplayNamePredicate(StringMatcher.parse(jsonElement)) - } - } -} diff --git a/src/main/kotlin/features/texturepack/ExtraAttributesPredicate.kt b/src/main/kotlin/features/texturepack/ExtraAttributesPredicate.kt deleted file mode 100644 index 4114f45..0000000 --- a/src/main/kotlin/features/texturepack/ExtraAttributesPredicate.kt +++ /dev/null @@ -1,268 +0,0 @@ - -package moe.nea.firmament.features.texturepack - -import com.google.gson.JsonArray -import com.google.gson.JsonElement -import com.google.gson.JsonObject -import com.google.gson.JsonPrimitive -import net.minecraft.item.ItemStack -import net.minecraft.nbt.NbtByte -import net.minecraft.nbt.NbtCompound -import net.minecraft.nbt.NbtDouble -import net.minecraft.nbt.NbtElement -import net.minecraft.nbt.NbtFloat -import net.minecraft.nbt.NbtInt -import net.minecraft.nbt.NbtList -import net.minecraft.nbt.NbtLong -import net.minecraft.nbt.NbtShort -import net.minecraft.nbt.NbtString -import moe.nea.firmament.util.extraAttributes - -fun interface NbtMatcher { - fun matches(nbt: NbtElement): Boolean - - object Parser { - fun parse(jsonElement: JsonElement): NbtMatcher? { - if (jsonElement is JsonPrimitive) { - if (jsonElement.isString) { - val string = jsonElement.asString - return MatchStringExact(string) - } - if (jsonElement.isNumber) { - return MatchNumberExact(jsonElement.asLong) //TODO: parse generic number - } - } - if (jsonElement is JsonObject) { - var encounteredParser: NbtMatcher? = null - for (entry in ExclusiveParserType.entries) { - val data = jsonElement[entry.key] ?: continue - if (encounteredParser != null) { - // TODO: warn - return null - } - encounteredParser = entry.parse(data) ?: return null - } - return encounteredParser - } - return null - } - - enum class ExclusiveParserType(val key: String) { - STRING("string") { - override fun parse(element: JsonElement): NbtMatcher? { - return MatchString(StringMatcher.parse(element)) - } - }, - INT("int") { - override fun parse(element: JsonElement): NbtMatcher? { - return parseGenericNumber(element, - { it.asInt }, - { (it as? NbtInt)?.intValue() }, - { a, b -> - if (a == b) Comparison.EQUAL - else if (a < b) Comparison.LESS_THAN - else Comparison.GREATER - }) - } - }, - FLOAT("float") { - override fun parse(element: JsonElement): NbtMatcher? { - return parseGenericNumber(element, - { it.asFloat }, - { (it as? NbtFloat)?.floatValue() }, - { a, b -> - if (a == b) Comparison.EQUAL - else if (a < b) Comparison.LESS_THAN - else Comparison.GREATER - }) - } - }, - DOUBLE("double") { - override fun parse(element: JsonElement): NbtMatcher? { - return parseGenericNumber(element, - { it.asDouble }, - { (it as? NbtDouble)?.doubleValue() }, - { a, b -> - if (a == b) Comparison.EQUAL - else if (a < b) Comparison.LESS_THAN - else Comparison.GREATER - }) - } - }, - LONG("long") { - override fun parse(element: JsonElement): NbtMatcher? { - return parseGenericNumber(element, - { it.asLong }, - { (it as? NbtLong)?.longValue() }, - { a, b -> - if (a == b) Comparison.EQUAL - else if (a < b) Comparison.LESS_THAN - else Comparison.GREATER - }) - } - }, - SHORT("short") { - override fun parse(element: JsonElement): NbtMatcher? { - return parseGenericNumber(element, - { it.asShort }, - { (it as? NbtShort)?.shortValue() }, - { a, b -> - if (a == b) Comparison.EQUAL - else if (a < b) Comparison.LESS_THAN - else Comparison.GREATER - }) - } - }, - BYTE("byte") { - override fun parse(element: JsonElement): NbtMatcher? { - return parseGenericNumber(element, - { it.asByte }, - { (it as? NbtByte)?.byteValue() }, - { a, b -> - if (a == b) Comparison.EQUAL - else if (a < b) Comparison.LESS_THAN - else Comparison.GREATER - }) - } - }, - ; - - abstract fun parse(element: JsonElement): NbtMatcher? - } - - enum class Comparison { - LESS_THAN, EQUAL, GREATER - } - - inline fun parseGenericNumber( - jsonElement: JsonElement, - primitiveExtractor: (JsonPrimitive) -> T?, - crossinline nbtExtractor: (NbtElement) -> T?, - crossinline compare: (T, T) -> Comparison - ): NbtMatcher? { - if (jsonElement is JsonPrimitive) { - val expected = primitiveExtractor(jsonElement) ?: return null - return NbtMatcher { - val actual = nbtExtractor(it) ?: return@NbtMatcher false - compare(actual, expected) == Comparison.EQUAL - } - } - if (jsonElement is JsonObject) { - val minElement = jsonElement.getAsJsonPrimitive("min") - val min = if (minElement != null) primitiveExtractor(minElement) ?: return null else null - val minExclusive = jsonElement.get("minExclusive")?.asBoolean ?: false - val maxElement = jsonElement.getAsJsonPrimitive("max") - val max = if (maxElement != null) primitiveExtractor(maxElement) ?: return null else null - val maxExclusive = jsonElement.get("maxExclusive")?.asBoolean ?: true - if (min == null && max == null) return null - return NbtMatcher { - val actual = nbtExtractor(it) ?: return@NbtMatcher false - if (max != null) { - val comp = compare(actual, max) - if (comp == Comparison.GREATER) return@NbtMatcher false - if (comp == Comparison.EQUAL && maxExclusive) return@NbtMatcher false - } - if (min != null) { - val comp = compare(actual, min) - if (comp == Comparison.LESS_THAN) return@NbtMatcher false - if (comp == Comparison.EQUAL && minExclusive) return@NbtMatcher false - } - return@NbtMatcher true - } - } - return null - - } - } - - class MatchNumberExact(val number: Long) : NbtMatcher { - override fun matches(nbt: NbtElement): Boolean { - return when (nbt) { - is NbtByte -> nbt.byteValue().toLong() == number - is NbtInt -> nbt.intValue().toLong() == number - is NbtShort -> nbt.shortValue().toLong() == number - is NbtLong -> nbt.longValue().toLong() == number - else -> false - } - } - - } - - class MatchStringExact(val string: String) : NbtMatcher { - override fun matches(nbt: NbtElement): Boolean { - return nbt is NbtString && nbt.asString() == string - } - - override fun toString(): String { - return "MatchNbtStringExactly($string)" - } - } - - class MatchString(val string: StringMatcher) : NbtMatcher { - override fun matches(nbt: NbtElement): Boolean { - return nbt is NbtString && string.matches(nbt.asString()) - } - - override fun toString(): String { - return "MatchNbtString($string)" - } - } -} - -data class ExtraAttributesPredicate( - val path: NbtPrism, - val matcher: NbtMatcher, -) : FirmamentModelPredicate { - - object Parser : FirmamentModelPredicateParser { - override fun parse(jsonElement: JsonElement): FirmamentModelPredicate? { - if (jsonElement !is JsonObject) return null - val path = jsonElement.get("path") ?: return null - val pathSegments = if (path is JsonArray) { - path.map { (it as JsonPrimitive).asString } - } else if (path is JsonPrimitive && path.isString) { - path.asString.split(".") - } else return null - val matcher = NbtMatcher.Parser.parse(jsonElement.get("match") ?: jsonElement) - ?: return null - return ExtraAttributesPredicate(NbtPrism(pathSegments), matcher) - } - } - - override fun test(stack: ItemStack): Boolean { - return path.access(stack.extraAttributes) - .any { matcher.matches(it) } - } -} - -class NbtPrism(val path: List) { - override fun toString(): String { - return "Prism($path)" - } - fun access(root: NbtElement): Collection { - var rootSet = mutableListOf(root) - var switch = mutableListOf() - for (pathSegment in path) { - if (pathSegment == ".") continue - for (element in rootSet) { - if (element is NbtList) { - if (pathSegment == "*") - switch.addAll(element) - val index = pathSegment.toIntOrNull() ?: continue - if (index !in element.indices) continue - switch.add(element[index]) - } - if (element is NbtCompound) { - if (pathSegment == "*") - element.keys.mapTo(switch) { element.get(it)!! } - switch.add(element.get(pathSegment) ?: continue) - } - } - val temp = switch - switch = rootSet - rootSet = temp - switch.clear() - } - return rootSet - } -} diff --git a/src/main/kotlin/features/texturepack/ItemPredicate.kt b/src/main/kotlin/features/texturepack/ItemPredicate.kt deleted file mode 100644 index 4302b53..0000000 --- a/src/main/kotlin/features/texturepack/ItemPredicate.kt +++ /dev/null @@ -1,32 +0,0 @@ - -package moe.nea.firmament.features.texturepack - -import com.google.gson.JsonElement -import com.google.gson.JsonPrimitive -import kotlin.jvm.optionals.getOrNull -import net.minecraft.item.Item -import net.minecraft.item.ItemStack -import net.minecraft.registry.RegistryKey -import net.minecraft.registry.RegistryKeys -import net.minecraft.util.Identifier -import moe.nea.firmament.util.MC - -class ItemPredicate( - val item: Item -) : FirmamentModelPredicate { - override fun test(stack: ItemStack): Boolean { - return stack.item == item - } - - object Parser : FirmamentModelPredicateParser { - override fun parse(jsonElement: JsonElement): ItemPredicate? { - if (jsonElement is JsonPrimitive && jsonElement.isString) { - val itemKey = RegistryKey.of(RegistryKeys.ITEM, - Identifier.tryParse(jsonElement.asString) - ?: return null) - return ItemPredicate(MC.defaultItems.getOptional(itemKey).getOrNull()?.value() ?: return null) - } - return null - } - } -} diff --git a/src/main/kotlin/features/texturepack/JsonUnbakedModelFirmExtra.kt b/src/main/kotlin/features/texturepack/JsonUnbakedModelFirmExtra.kt index ab9e27d..0d0f8f2 100644 --- a/src/main/kotlin/features/texturepack/JsonUnbakedModelFirmExtra.kt +++ b/src/main/kotlin/features/texturepack/JsonUnbakedModelFirmExtra.kt @@ -7,4 +7,8 @@ interface JsonUnbakedModelFirmExtra { fun setHeadModel_firmament(identifier: Identifier?) fun getHeadModel_firmament(): Identifier? + + fun setTintOverrides_firmament(tintOverrides: TintOverrides?) + fun getTintOverrides_firmament(): TintOverrides + } diff --git a/src/main/kotlin/features/texturepack/LorePredicate.kt b/src/main/kotlin/features/texturepack/LorePredicate.kt deleted file mode 100644 index f2b7e76..0000000 --- a/src/main/kotlin/features/texturepack/LorePredicate.kt +++ /dev/null @@ -1,19 +0,0 @@ - -package moe.nea.firmament.features.texturepack - -import com.google.gson.JsonElement -import net.minecraft.item.ItemStack -import moe.nea.firmament.util.mc.loreAccordingToNbt - -class LorePredicate(val matcher: StringMatcher) : FirmamentModelPredicate { - object Parser : FirmamentModelPredicateParser { - override fun parse(jsonElement: JsonElement): FirmamentModelPredicate { - return LorePredicate(StringMatcher.parse(jsonElement)) - } - } - - override fun test(stack: ItemStack): Boolean { - val lore = stack.loreAccordingToNbt - return lore.any { matcher.matches(it) } - } -} diff --git a/src/main/kotlin/features/texturepack/ModelOverrideFilterSet.kt b/src/main/kotlin/features/texturepack/ModelOverrideFilterSet.kt deleted file mode 100644 index 4ef8d06..0000000 --- a/src/main/kotlin/features/texturepack/ModelOverrideFilterSet.kt +++ /dev/null @@ -1,19 +0,0 @@ - -package moe.nea.firmament.features.texturepack - -import com.google.gson.JsonElement -import moe.nea.firmament.util.filter.IteratorFilterSet - -class ModelOverrideFilterSet(original: java.util.Set>) : - IteratorFilterSet>(original) { - companion object { - @JvmStatic - fun createFilterSet(set: java.util.Set<*>): java.util.Set<*> { - return ModelOverrideFilterSet(set as java.util.Set>) as java.util.Set<*> - } - } - - override fun shouldKeepElement(element: Map.Entry): Boolean { - return !element.key.startsWith("firmament:") - } -} diff --git a/src/main/kotlin/features/texturepack/NotPredicate.kt b/src/main/kotlin/features/texturepack/NotPredicate.kt deleted file mode 100644 index ecd67c3..0000000 --- a/src/main/kotlin/features/texturepack/NotPredicate.kt +++ /dev/null @@ -1,18 +0,0 @@ - -package moe.nea.firmament.features.texturepack - -import com.google.gson.JsonElement -import com.google.gson.JsonObject -import net.minecraft.item.ItemStack - -class NotPredicate(val children: Array) : FirmamentModelPredicate { - override fun test(stack: ItemStack): Boolean { - return children.none { it.test(stack) } - } - - object Parser : FirmamentModelPredicateParser { - override fun parse(jsonElement: JsonElement): FirmamentModelPredicate { - return NotPredicate(CustomModelOverrideParser.parsePredicates(jsonElement as JsonObject).toTypedArray()) - } - } -} diff --git a/src/main/kotlin/features/texturepack/NumberMatcher.kt b/src/main/kotlin/features/texturepack/NumberMatcher.kt deleted file mode 100644 index e6f2d01..0000000 --- a/src/main/kotlin/features/texturepack/NumberMatcher.kt +++ /dev/null @@ -1,124 +0,0 @@ -package moe.nea.firmament.features.texturepack - -import com.google.gson.JsonElement -import com.google.gson.JsonPrimitive -import moe.nea.firmament.util.useMatch - -abstract class NumberMatcher { - abstract fun test(number: Number): Boolean - - - companion object { - fun parse(jsonElement: JsonElement): NumberMatcher? { - if (jsonElement is JsonPrimitive) { - if (jsonElement.isString) { - val string = jsonElement.asString - return parseRange(string) ?: parseOperator(string) - } - if (jsonElement.isNumber) { - val number = jsonElement.asNumber - val hasDecimals = (number.toString().contains(".")) - return MatchNumberExact(if (hasDecimals) number.toLong() else number.toDouble()) - } - } - return null - } - - private val intervalSpec = - "(?[\\[\\(])(?[0-9.]+)?,(?[0-9.]+)?(?[\\]\\)])" - .toPattern() - - fun parseRange(string: String): RangeMatcher? { - intervalSpec.useMatch(string) { - // Open in the set-theory sense, meaning does not include its end. - val beginningOpen = group("beginningOpen") == "(" - val endingOpen = group("endingOpen") == ")" - val beginning = group("beginning")?.toDouble() - val ending = group("ending")?.toDouble() - return RangeMatcher(beginning, !beginningOpen, ending, !endingOpen) - } - return null - } - - enum class Operator(val operator: String) { - LESS("<") { - override fun matches(comparisonResult: Int): Boolean { - return comparisonResult < 0 - } - }, - LESS_EQUALS("<=") { - override fun matches(comparisonResult: Int): Boolean { - return comparisonResult <= 0 - } - }, - GREATER(">") { - override fun matches(comparisonResult: Int): Boolean { - return comparisonResult > 0 - } - }, - GREATER_EQUALS(">=") { - override fun matches(comparisonResult: Int): Boolean { - return comparisonResult >= 0 - } - }, - ; - - abstract fun matches(comparisonResult: Int): Boolean - } - - private val operatorPattern = - "(?${Operator.entries.joinToString("|") { it.operator }})(?[0-9.]+)".toPattern() - - fun parseOperator(string: String): OperatorMatcher? { - return operatorPattern.useMatch(string) { - val operatorName = group("operator") - val operator = Operator.entries.find { it.operator == operatorName }!! - val value = group("value").toDouble() - OperatorMatcher(operator, value) - } - } - - data class OperatorMatcher(val operator: Operator, val value: Double) : NumberMatcher() { - override fun test(number: Number): Boolean { - return operator.matches(number.toDouble().compareTo(value)) - } - } - - - data class MatchNumberExact(val number: Number) : NumberMatcher() { - override fun test(number: Number): Boolean { - return when (this.number) { - is Double -> number.toDouble() == this.number.toDouble() - else -> number.toLong() == this.number.toLong() - } - } - } - - data class RangeMatcher( - val beginning: Double?, - val beginningInclusive: Boolean, - val ending: Double?, - val endingInclusive: Boolean, - ) : NumberMatcher() { - override fun test(number: Number): Boolean { - val value = number.toDouble() - if (beginning != null) { - if (beginningInclusive) { - if (value < beginning) return false - } else { - if (value <= beginning) return false - } - } - if (ending != null) { - if (endingInclusive) { - if (value > ending) return false - } else { - if (value >= ending) return false - } - } - return true - } - } - } - -} diff --git a/src/main/kotlin/features/texturepack/OrPredicate.kt b/src/main/kotlin/features/texturepack/OrPredicate.kt deleted file mode 100644 index 32f556b..0000000 --- a/src/main/kotlin/features/texturepack/OrPredicate.kt +++ /dev/null @@ -1,26 +0,0 @@ - -package moe.nea.firmament.features.texturepack - -import com.google.gson.JsonArray -import com.google.gson.JsonElement -import com.google.gson.JsonObject -import net.minecraft.item.ItemStack - -class OrPredicate(val children: Array) : FirmamentModelPredicate { - override fun test(stack: ItemStack): Boolean { - return children.any { it.test(stack) } - } - - object Parser : FirmamentModelPredicateParser { - override fun parse(jsonElement: JsonElement): FirmamentModelPredicate { - val children = - (jsonElement as JsonArray) - .flatMap { - CustomModelOverrideParser.parsePredicates(it as JsonObject) - } - .toTypedArray() - return OrPredicate(children) - } - - } -} diff --git a/src/main/kotlin/features/texturepack/PetPredicate.kt b/src/main/kotlin/features/texturepack/PetPredicate.kt deleted file mode 100644 index 5e5d750..0000000 --- a/src/main/kotlin/features/texturepack/PetPredicate.kt +++ /dev/null @@ -1,66 +0,0 @@ - -package moe.nea.firmament.features.texturepack - -import com.google.gson.JsonElement -import com.google.gson.JsonObject -import net.minecraft.item.ItemStack -import moe.nea.firmament.repo.ExpLadders -import moe.nea.firmament.util.petData - -class PetPredicate( - val petId: StringMatcher?, - val tier: RarityMatcher?, - val exp: NumberMatcher?, - val candyUsed: NumberMatcher?, - val level: NumberMatcher?, -) : FirmamentModelPredicate { - - override fun test(stack: ItemStack): Boolean { - val petData = stack.petData ?: return false - if (petId != null) { - if (!petId.matches(petData.type)) return false - } - if (exp != null) { - if (!exp.test(petData.exp)) return false - } - if (candyUsed != null) { - if (!candyUsed.test(petData.candyUsed)) return false - } - if (tier != null) { - if (!tier.match(petData.tier)) return false - } - val levelData by lazy(LazyThreadSafetyMode.NONE) { - ExpLadders.getExpLadder(petData.type, petData.tier) - .getPetLevel(petData.exp) - } - if (level != null) { - if (!level.test(levelData.currentLevel)) return false - } - return true - } - - object Parser : FirmamentModelPredicateParser { - override fun parse(jsonElement: JsonElement): FirmamentModelPredicate? { - if (jsonElement.isJsonPrimitive) { - return PetPredicate(StringMatcher.Equals(jsonElement.asString, false), null, null, null, null) - } - if (jsonElement !is JsonObject) return null - val idMatcher = jsonElement["id"]?.let(StringMatcher::parse) - val expMatcher = jsonElement["exp"]?.let(NumberMatcher::parse) - val levelMatcher = jsonElement["level"]?.let(NumberMatcher::parse) - val candyMatcher = jsonElement["candyUsed"]?.let(NumberMatcher::parse) - val tierMatcher = jsonElement["tier"]?.let(RarityMatcher::parse) - return PetPredicate( - idMatcher, - tierMatcher, - expMatcher, - candyMatcher, - levelMatcher, - ) - } - } - - override fun toString(): String { - return super.toString() - } -} diff --git a/src/main/kotlin/features/texturepack/TintOverrides.kt b/src/main/kotlin/features/texturepack/TintOverrides.kt new file mode 100644 index 0000000..8006db8 --- /dev/null +++ b/src/main/kotlin/features/texturepack/TintOverrides.kt @@ -0,0 +1,75 @@ +package moe.nea.firmament.features.texturepack + +import com.google.gson.JsonObject +import com.google.gson.JsonPrimitive +import moe.nea.firmament.util.ErrorUtil +import moe.nea.firmament.util.assertNotNullOr + +data class TintOverrides( + val layerMap: Map = mapOf() +) { + val hasOverrides by lazy { layerMap.values.any { it !is Reset } } + + companion object { + val EMPTY = TintOverrides() + private val threadLocal = object : ThreadLocal() {} + fun enter(overrides: TintOverrides?) { + ErrorUtil.softCheck("Double entered tintOverrides") { + threadLocal.get() == null + } + threadLocal.set(overrides ?: EMPTY) + } + + fun exit(overrides: TintOverrides?) { + ErrorUtil.softCheck("Exited with non matching enter tintOverrides") { + threadLocal.get() == (overrides ?: EMPTY) + } + threadLocal.remove() + } + + fun getCurrentOverrides() = + assertNotNullOr(threadLocal.get(), "Got current tintOverrides without entering") { EMPTY } + + fun parse(jsonObject: JsonObject): TintOverrides { + val map = mutableMapOf() + for ((key, value) in jsonObject.entrySet()) { + val layerIndex = + ErrorUtil.notNullOr(key.toIntOrNull(), + "Unknown layer index $value. Should be integer") { continue } + if (value.isJsonNull) { + map[layerIndex] = Reset + continue + } + val override = (value as? JsonPrimitive) + ?.takeIf(JsonPrimitive::isNumber) + ?.asInt + ?.let(::Fixed) + if (override == null) { + ErrorUtil.softError("Invalid tint override for a layer: $value") + continue + } + map[layerIndex] = override + } + return TintOverrides(map) + } + } + + fun mergeWithParent(parent: TintOverrides): TintOverrides { + val mergedMap = parent.layerMap.toMutableMap() + mergedMap.putAll(this.layerMap) + return TintOverrides(mergedMap) + } + + fun hasOverrides(): Boolean = hasOverrides + fun getOverride(tintIndex: Int): Int? { + return when (val tint = layerMap[tintIndex]) { + is Reset -> null + is Fixed -> tint.color + null -> null + } + } + + sealed interface TintOverride + data object Reset : TintOverride + data class Fixed(val color: Int) : TintOverride +} diff --git a/src/main/kotlin/features/texturepack/predicates/AlwaysPredicate.kt b/src/main/kotlin/features/texturepack/predicates/AlwaysPredicate.kt new file mode 100644 index 0000000..7e0ddb1 --- /dev/null +++ b/src/main/kotlin/features/texturepack/predicates/AlwaysPredicate.kt @@ -0,0 +1,19 @@ + +package moe.nea.firmament.features.texturepack.predicates + +import com.google.gson.JsonElement +import moe.nea.firmament.features.texturepack.FirmamentModelPredicate +import moe.nea.firmament.features.texturepack.FirmamentModelPredicateParser +import net.minecraft.item.ItemStack + +object AlwaysPredicate : FirmamentModelPredicate { + override fun test(stack: ItemStack): Boolean { + return true + } + + object Parser : FirmamentModelPredicateParser { + override fun parse(jsonElement: JsonElement): FirmamentModelPredicate { + return AlwaysPredicate + } + } +} diff --git a/src/main/kotlin/features/texturepack/predicates/AndPredicate.kt b/src/main/kotlin/features/texturepack/predicates/AndPredicate.kt new file mode 100644 index 0000000..99abaaa --- /dev/null +++ b/src/main/kotlin/features/texturepack/predicates/AndPredicate.kt @@ -0,0 +1,28 @@ +package moe.nea.firmament.features.texturepack.predicates + +import com.google.gson.JsonArray +import com.google.gson.JsonElement +import com.google.gson.JsonObject +import moe.nea.firmament.features.texturepack.CustomModelOverrideParser +import moe.nea.firmament.features.texturepack.FirmamentModelPredicate +import moe.nea.firmament.features.texturepack.FirmamentModelPredicateParser +import net.minecraft.item.ItemStack + +class AndPredicate(val children: Array) : FirmamentModelPredicate { + override fun test(stack: ItemStack): Boolean { + return children.all { it.test(stack) } + } + + object Parser : FirmamentModelPredicateParser { + override fun parse(jsonElement: JsonElement): FirmamentModelPredicate { + val children = + (jsonElement as JsonArray) + .flatMap { + CustomModelOverrideParser.parsePredicates(it as JsonObject) + } + .toTypedArray() + return AndPredicate(children) + } + + } +} diff --git a/src/main/kotlin/features/texturepack/predicates/DisplayNamePredicate.kt b/src/main/kotlin/features/texturepack/predicates/DisplayNamePredicate.kt new file mode 100644 index 0000000..04c7a2b --- /dev/null +++ b/src/main/kotlin/features/texturepack/predicates/DisplayNamePredicate.kt @@ -0,0 +1,22 @@ + +package moe.nea.firmament.features.texturepack.predicates + +import com.google.gson.JsonElement +import moe.nea.firmament.features.texturepack.FirmamentModelPredicate +import moe.nea.firmament.features.texturepack.FirmamentModelPredicateParser +import moe.nea.firmament.features.texturepack.StringMatcher +import net.minecraft.item.ItemStack +import moe.nea.firmament.util.mc.displayNameAccordingToNbt + +data class DisplayNamePredicate(val stringMatcher: StringMatcher) : FirmamentModelPredicate { + override fun test(stack: ItemStack): Boolean { + val display = stack.displayNameAccordingToNbt + return stringMatcher.matches(display) + } + + object Parser : FirmamentModelPredicateParser { + override fun parse(jsonElement: JsonElement): FirmamentModelPredicate { + return DisplayNamePredicate(StringMatcher.parse(jsonElement)) + } + } +} diff --git a/src/main/kotlin/features/texturepack/predicates/ExtraAttributesPredicate.kt b/src/main/kotlin/features/texturepack/predicates/ExtraAttributesPredicate.kt new file mode 100644 index 0000000..3c8023d --- /dev/null +++ b/src/main/kotlin/features/texturepack/predicates/ExtraAttributesPredicate.kt @@ -0,0 +1,271 @@ + +package moe.nea.firmament.features.texturepack.predicates + +import com.google.gson.JsonArray +import com.google.gson.JsonElement +import com.google.gson.JsonObject +import com.google.gson.JsonPrimitive +import moe.nea.firmament.features.texturepack.FirmamentModelPredicate +import moe.nea.firmament.features.texturepack.FirmamentModelPredicateParser +import moe.nea.firmament.features.texturepack.StringMatcher +import net.minecraft.item.ItemStack +import net.minecraft.nbt.NbtByte +import net.minecraft.nbt.NbtCompound +import net.minecraft.nbt.NbtDouble +import net.minecraft.nbt.NbtElement +import net.minecraft.nbt.NbtFloat +import net.minecraft.nbt.NbtInt +import net.minecraft.nbt.NbtList +import net.minecraft.nbt.NbtLong +import net.minecraft.nbt.NbtShort +import net.minecraft.nbt.NbtString +import moe.nea.firmament.util.extraAttributes + +fun interface NbtMatcher { + fun matches(nbt: NbtElement): Boolean + + object Parser { + fun parse(jsonElement: JsonElement): NbtMatcher? { + if (jsonElement is JsonPrimitive) { + if (jsonElement.isString) { + val string = jsonElement.asString + return MatchStringExact(string) + } + if (jsonElement.isNumber) { + return MatchNumberExact(jsonElement.asLong) //TODO: parse generic number + } + } + if (jsonElement is JsonObject) { + var encounteredParser: NbtMatcher? = null + for (entry in ExclusiveParserType.entries) { + val data = jsonElement[entry.key] ?: continue + if (encounteredParser != null) { + // TODO: warn + return null + } + encounteredParser = entry.parse(data) ?: return null + } + return encounteredParser + } + return null + } + + enum class ExclusiveParserType(val key: String) { + STRING("string") { + override fun parse(element: JsonElement): NbtMatcher? { + return MatchString(StringMatcher.parse(element)) + } + }, + INT("int") { + override fun parse(element: JsonElement): NbtMatcher? { + return parseGenericNumber(element, + { it.asInt }, + { (it as? NbtInt)?.intValue() }, + { a, b -> + if (a == b) Comparison.EQUAL + else if (a < b) Comparison.LESS_THAN + else Comparison.GREATER + }) + } + }, + FLOAT("float") { + override fun parse(element: JsonElement): NbtMatcher? { + return parseGenericNumber(element, + { it.asFloat }, + { (it as? NbtFloat)?.floatValue() }, + { a, b -> + if (a == b) Comparison.EQUAL + else if (a < b) Comparison.LESS_THAN + else Comparison.GREATER + }) + } + }, + DOUBLE("double") { + override fun parse(element: JsonElement): NbtMatcher? { + return parseGenericNumber(element, + { it.asDouble }, + { (it as? NbtDouble)?.doubleValue() }, + { a, b -> + if (a == b) Comparison.EQUAL + else if (a < b) Comparison.LESS_THAN + else Comparison.GREATER + }) + } + }, + LONG("long") { + override fun parse(element: JsonElement): NbtMatcher? { + return parseGenericNumber(element, + { it.asLong }, + { (it as? NbtLong)?.longValue() }, + { a, b -> + if (a == b) Comparison.EQUAL + else if (a < b) Comparison.LESS_THAN + else Comparison.GREATER + }) + } + }, + SHORT("short") { + override fun parse(element: JsonElement): NbtMatcher? { + return parseGenericNumber(element, + { it.asShort }, + { (it as? NbtShort)?.shortValue() }, + { a, b -> + if (a == b) Comparison.EQUAL + else if (a < b) Comparison.LESS_THAN + else Comparison.GREATER + }) + } + }, + BYTE("byte") { + override fun parse(element: JsonElement): NbtMatcher? { + return parseGenericNumber(element, + { it.asByte }, + { (it as? NbtByte)?.byteValue() }, + { a, b -> + if (a == b) Comparison.EQUAL + else if (a < b) Comparison.LESS_THAN + else Comparison.GREATER + }) + } + }, + ; + + abstract fun parse(element: JsonElement): NbtMatcher? + } + + enum class Comparison { + LESS_THAN, EQUAL, GREATER + } + + inline fun parseGenericNumber( + jsonElement: JsonElement, + primitiveExtractor: (JsonPrimitive) -> T?, + crossinline nbtExtractor: (NbtElement) -> T?, + crossinline compare: (T, T) -> Comparison + ): NbtMatcher? { + if (jsonElement is JsonPrimitive) { + val expected = primitiveExtractor(jsonElement) ?: return null + return NbtMatcher { + val actual = nbtExtractor(it) ?: return@NbtMatcher false + compare(actual, expected) == Comparison.EQUAL + } + } + if (jsonElement is JsonObject) { + val minElement = jsonElement.getAsJsonPrimitive("min") + val min = if (minElement != null) primitiveExtractor(minElement) ?: return null else null + val minExclusive = jsonElement.get("minExclusive")?.asBoolean ?: false + val maxElement = jsonElement.getAsJsonPrimitive("max") + val max = if (maxElement != null) primitiveExtractor(maxElement) ?: return null else null + val maxExclusive = jsonElement.get("maxExclusive")?.asBoolean ?: true + if (min == null && max == null) return null + return NbtMatcher { + val actual = nbtExtractor(it) ?: return@NbtMatcher false + if (max != null) { + val comp = compare(actual, max) + if (comp == Comparison.GREATER) return@NbtMatcher false + if (comp == Comparison.EQUAL && maxExclusive) return@NbtMatcher false + } + if (min != null) { + val comp = compare(actual, min) + if (comp == Comparison.LESS_THAN) return@NbtMatcher false + if (comp == Comparison.EQUAL && minExclusive) return@NbtMatcher false + } + return@NbtMatcher true + } + } + return null + + } + } + + class MatchNumberExact(val number: Long) : NbtMatcher { + override fun matches(nbt: NbtElement): Boolean { + return when (nbt) { + is NbtByte -> nbt.byteValue().toLong() == number + is NbtInt -> nbt.intValue().toLong() == number + is NbtShort -> nbt.shortValue().toLong() == number + is NbtLong -> nbt.longValue().toLong() == number + else -> false + } + } + + } + + class MatchStringExact(val string: String) : NbtMatcher { + override fun matches(nbt: NbtElement): Boolean { + return nbt is NbtString && nbt.asString() == string + } + + override fun toString(): String { + return "MatchNbtStringExactly($string)" + } + } + + class MatchString(val string: StringMatcher) : NbtMatcher { + override fun matches(nbt: NbtElement): Boolean { + return nbt is NbtString && string.matches(nbt.asString()) + } + + override fun toString(): String { + return "MatchNbtString($string)" + } + } +} + +data class ExtraAttributesPredicate( + val path: NbtPrism, + val matcher: NbtMatcher, +) : FirmamentModelPredicate { + + object Parser : FirmamentModelPredicateParser { + override fun parse(jsonElement: JsonElement): FirmamentModelPredicate? { + if (jsonElement !is JsonObject) return null + val path = jsonElement.get("path") ?: return null + val pathSegments = if (path is JsonArray) { + path.map { (it as JsonPrimitive).asString } + } else if (path is JsonPrimitive && path.isString) { + path.asString.split(".") + } else return null + val matcher = NbtMatcher.Parser.parse(jsonElement.get("match") ?: jsonElement) + ?: return null + return ExtraAttributesPredicate(NbtPrism(pathSegments), matcher) + } + } + + override fun test(stack: ItemStack): Boolean { + return path.access(stack.extraAttributes) + .any { matcher.matches(it) } + } +} + +class NbtPrism(val path: List) { + override fun toString(): String { + return "Prism($path)" + } + fun access(root: NbtElement): Collection { + var rootSet = mutableListOf(root) + var switch = mutableListOf() + for (pathSegment in path) { + if (pathSegment == ".") continue + for (element in rootSet) { + if (element is NbtList) { + if (pathSegment == "*") + switch.addAll(element) + val index = pathSegment.toIntOrNull() ?: continue + if (index !in element.indices) continue + switch.add(element[index]) + } + if (element is NbtCompound) { + if (pathSegment == "*") + element.keys.mapTo(switch) { element.get(it)!! } + switch.add(element.get(pathSegment) ?: continue) + } + } + val temp = switch + switch = rootSet + rootSet = temp + switch.clear() + } + return rootSet + } +} diff --git a/src/main/kotlin/features/texturepack/predicates/ItemPredicate.kt b/src/main/kotlin/features/texturepack/predicates/ItemPredicate.kt new file mode 100644 index 0000000..3cb80c7 --- /dev/null +++ b/src/main/kotlin/features/texturepack/predicates/ItemPredicate.kt @@ -0,0 +1,34 @@ + +package moe.nea.firmament.features.texturepack.predicates + +import com.google.gson.JsonElement +import com.google.gson.JsonPrimitive +import moe.nea.firmament.features.texturepack.FirmamentModelPredicate +import moe.nea.firmament.features.texturepack.FirmamentModelPredicateParser +import kotlin.jvm.optionals.getOrNull +import net.minecraft.item.Item +import net.minecraft.item.ItemStack +import net.minecraft.registry.RegistryKey +import net.minecraft.registry.RegistryKeys +import net.minecraft.util.Identifier +import moe.nea.firmament.util.MC + +class ItemPredicate( + val item: Item +) : FirmamentModelPredicate { + override fun test(stack: ItemStack): Boolean { + return stack.item == item + } + + object Parser : FirmamentModelPredicateParser { + override fun parse(jsonElement: JsonElement): ItemPredicate? { + if (jsonElement is JsonPrimitive && jsonElement.isString) { + val itemKey = RegistryKey.of(RegistryKeys.ITEM, + Identifier.tryParse(jsonElement.asString) + ?: return null) + return ItemPredicate(MC.defaultItems.getOptional(itemKey).getOrNull()?.value() ?: return null) + } + return null + } + } +} diff --git a/src/main/kotlin/features/texturepack/predicates/LorePredicate.kt b/src/main/kotlin/features/texturepack/predicates/LorePredicate.kt new file mode 100644 index 0000000..f0b4737 --- /dev/null +++ b/src/main/kotlin/features/texturepack/predicates/LorePredicate.kt @@ -0,0 +1,22 @@ + +package moe.nea.firmament.features.texturepack.predicates + +import com.google.gson.JsonElement +import moe.nea.firmament.features.texturepack.FirmamentModelPredicate +import moe.nea.firmament.features.texturepack.FirmamentModelPredicateParser +import moe.nea.firmament.features.texturepack.StringMatcher +import net.minecraft.item.ItemStack +import moe.nea.firmament.util.mc.loreAccordingToNbt + +class LorePredicate(val matcher: StringMatcher) : FirmamentModelPredicate { + object Parser : FirmamentModelPredicateParser { + override fun parse(jsonElement: JsonElement): FirmamentModelPredicate { + return LorePredicate(StringMatcher.parse(jsonElement)) + } + } + + override fun test(stack: ItemStack): Boolean { + val lore = stack.loreAccordingToNbt + return lore.any { matcher.matches(it) } + } +} diff --git a/src/main/kotlin/features/texturepack/predicates/NotPredicate.kt b/src/main/kotlin/features/texturepack/predicates/NotPredicate.kt new file mode 100644 index 0000000..4986ad9 --- /dev/null +++ b/src/main/kotlin/features/texturepack/predicates/NotPredicate.kt @@ -0,0 +1,21 @@ + +package moe.nea.firmament.features.texturepack.predicates + +import com.google.gson.JsonElement +import com.google.gson.JsonObject +import moe.nea.firmament.features.texturepack.CustomModelOverrideParser +import moe.nea.firmament.features.texturepack.FirmamentModelPredicate +import moe.nea.firmament.features.texturepack.FirmamentModelPredicateParser +import net.minecraft.item.ItemStack + +class NotPredicate(val children: Array) : FirmamentModelPredicate { + override fun test(stack: ItemStack): Boolean { + return children.none { it.test(stack) } + } + + object Parser : FirmamentModelPredicateParser { + override fun parse(jsonElement: JsonElement): FirmamentModelPredicate { + return NotPredicate(CustomModelOverrideParser.parsePredicates(jsonElement as JsonObject).toTypedArray()) + } + } +} diff --git a/src/main/kotlin/features/texturepack/predicates/NumberMatcher.kt b/src/main/kotlin/features/texturepack/predicates/NumberMatcher.kt new file mode 100644 index 0000000..b0d5178 --- /dev/null +++ b/src/main/kotlin/features/texturepack/predicates/NumberMatcher.kt @@ -0,0 +1,124 @@ +package moe.nea.firmament.features.texturepack.predicates + +import com.google.gson.JsonElement +import com.google.gson.JsonPrimitive +import moe.nea.firmament.util.useMatch + +abstract class NumberMatcher { + abstract fun test(number: Number): Boolean + + + companion object { + fun parse(jsonElement: JsonElement): NumberMatcher? { + if (jsonElement is JsonPrimitive) { + if (jsonElement.isString) { + val string = jsonElement.asString + return parseRange(string) ?: parseOperator(string) + } + if (jsonElement.isNumber) { + val number = jsonElement.asNumber + val hasDecimals = (number.toString().contains(".")) + return MatchNumberExact(if (hasDecimals) number.toLong() else number.toDouble()) + } + } + return null + } + + private val intervalSpec = + "(?[\\[\\(])(?[0-9.]+)?,(?[0-9.]+)?(?[\\]\\)])" + .toPattern() + + fun parseRange(string: String): RangeMatcher? { + intervalSpec.useMatch(string) { + // Open in the set-theory sense, meaning does not include its end. + val beginningOpen = group("beginningOpen") == "(" + val endingOpen = group("endingOpen") == ")" + val beginning = group("beginning")?.toDouble() + val ending = group("ending")?.toDouble() + return RangeMatcher(beginning, !beginningOpen, ending, !endingOpen) + } + return null + } + + enum class Operator(val operator: String) { + LESS("<") { + override fun matches(comparisonResult: Int): Boolean { + return comparisonResult < 0 + } + }, + LESS_EQUALS("<=") { + override fun matches(comparisonResult: Int): Boolean { + return comparisonResult <= 0 + } + }, + GREATER(">") { + override fun matches(comparisonResult: Int): Boolean { + return comparisonResult > 0 + } + }, + GREATER_EQUALS(">=") { + override fun matches(comparisonResult: Int): Boolean { + return comparisonResult >= 0 + } + }, + ; + + abstract fun matches(comparisonResult: Int): Boolean + } + + private val operatorPattern = + "(?${Operator.entries.joinToString("|") { it.operator }})(?[0-9.]+)".toPattern() + + fun parseOperator(string: String): OperatorMatcher? { + return operatorPattern.useMatch(string) { + val operatorName = group("operator") + val operator = Operator.entries.find { it.operator == operatorName }!! + val value = group("value").toDouble() + OperatorMatcher(operator, value) + } + } + + data class OperatorMatcher(val operator: Operator, val value: Double) : NumberMatcher() { + override fun test(number: Number): Boolean { + return operator.matches(number.toDouble().compareTo(value)) + } + } + + + data class MatchNumberExact(val number: Number) : NumberMatcher() { + override fun test(number: Number): Boolean { + return when (this.number) { + is Double -> number.toDouble() == this.number.toDouble() + else -> number.toLong() == this.number.toLong() + } + } + } + + data class RangeMatcher( + val beginning: Double?, + val beginningInclusive: Boolean, + val ending: Double?, + val endingInclusive: Boolean, + ) : NumberMatcher() { + override fun test(number: Number): Boolean { + val value = number.toDouble() + if (beginning != null) { + if (beginningInclusive) { + if (value < beginning) return false + } else { + if (value <= beginning) return false + } + } + if (ending != null) { + if (endingInclusive) { + if (value > ending) return false + } else { + if (value >= ending) return false + } + } + return true + } + } + } + +} diff --git a/src/main/kotlin/features/texturepack/predicates/OrPredicate.kt b/src/main/kotlin/features/texturepack/predicates/OrPredicate.kt new file mode 100644 index 0000000..e3093cd --- /dev/null +++ b/src/main/kotlin/features/texturepack/predicates/OrPredicate.kt @@ -0,0 +1,29 @@ + +package moe.nea.firmament.features.texturepack.predicates + +import com.google.gson.JsonArray +import com.google.gson.JsonElement +import com.google.gson.JsonObject +import moe.nea.firmament.features.texturepack.CustomModelOverrideParser +import moe.nea.firmament.features.texturepack.FirmamentModelPredicate +import moe.nea.firmament.features.texturepack.FirmamentModelPredicateParser +import net.minecraft.item.ItemStack + +class OrPredicate(val children: Array) : FirmamentModelPredicate { + override fun test(stack: ItemStack): Boolean { + return children.any { it.test(stack) } + } + + object Parser : FirmamentModelPredicateParser { + override fun parse(jsonElement: JsonElement): FirmamentModelPredicate { + val children = + (jsonElement as JsonArray) + .flatMap { + CustomModelOverrideParser.parsePredicates(it as JsonObject) + } + .toTypedArray() + return OrPredicate(children) + } + + } +} diff --git a/src/main/kotlin/features/texturepack/predicates/PetPredicate.kt b/src/main/kotlin/features/texturepack/predicates/PetPredicate.kt new file mode 100644 index 0000000..b30b7c9 --- /dev/null +++ b/src/main/kotlin/features/texturepack/predicates/PetPredicate.kt @@ -0,0 +1,66 @@ + +package moe.nea.firmament.features.texturepack.predicates + +import com.google.gson.JsonElement +import com.google.gson.JsonObject +import moe.nea.firmament.features.texturepack.FirmamentModelPredicate +import moe.nea.firmament.features.texturepack.FirmamentModelPredicateParser +import moe.nea.firmament.features.texturepack.RarityMatcher +import moe.nea.firmament.features.texturepack.StringMatcher +import net.minecraft.item.ItemStack +import moe.nea.firmament.repo.ExpLadders +import moe.nea.firmament.util.petData + +data class PetPredicate( + val petId: StringMatcher?, + val tier: RarityMatcher?, + val exp: NumberMatcher?, + val candyUsed: NumberMatcher?, + val level: NumberMatcher?, +) : FirmamentModelPredicate { + + override fun test(stack: ItemStack): Boolean { + val petData = stack.petData ?: return false + if (petId != null) { + if (!petId.matches(petData.type)) return false + } + if (exp != null) { + if (!exp.test(petData.exp)) return false + } + if (candyUsed != null) { + if (!candyUsed.test(petData.candyUsed)) return false + } + if (tier != null) { + if (!tier.match(petData.tier)) return false + } + val levelData by lazy(LazyThreadSafetyMode.NONE) { + ExpLadders.getExpLadder(petData.type, petData.tier) + .getPetLevel(petData.exp) + } + if (level != null) { + if (!level.test(levelData.currentLevel)) return false + } + return true + } + + object Parser : FirmamentModelPredicateParser { + override fun parse(jsonElement: JsonElement): FirmamentModelPredicate? { + if (jsonElement.isJsonPrimitive) { + return PetPredicate(StringMatcher.Equals(jsonElement.asString, false), null, null, null, null) + } + if (jsonElement !is JsonObject) return null + val idMatcher = jsonElement["id"]?.let(StringMatcher::parse) + val expMatcher = jsonElement["exp"]?.let(NumberMatcher::parse) + val levelMatcher = jsonElement["level"]?.let(NumberMatcher::parse) + val candyMatcher = jsonElement["candyUsed"]?.let(NumberMatcher::parse) + val tierMatcher = jsonElement["tier"]?.let(RarityMatcher::parse) + return PetPredicate( + idMatcher, + tierMatcher, + expMatcher, + candyMatcher, + levelMatcher, + ) + } + } +} diff --git a/src/main/kotlin/repo/RepoManager.kt b/src/main/kotlin/repo/RepoManager.kt index 9f8f422..c052fb9 100644 --- a/src/main/kotlin/repo/RepoManager.kt +++ b/src/main/kotlin/repo/RepoManager.kt @@ -9,13 +9,13 @@ import net.fabricmc.fabric.api.client.event.lifecycle.v1.ClientTickEvents import kotlinx.coroutines.launch import net.minecraft.client.MinecraftClient import net.minecraft.network.packet.s2c.play.SynchronizeRecipesS2CPacket -import net.minecraft.text.Text import moe.nea.firmament.Firmament import moe.nea.firmament.Firmament.logger import moe.nea.firmament.events.ReloadRegistrationEvent import moe.nea.firmament.gui.config.ManagedConfig import moe.nea.firmament.util.MinecraftDispatcher import moe.nea.firmament.util.SkyblockId +import moe.nea.firmament.util.tr object RepoManager { object Config : ManagedConfig("repo", Category.META) { @@ -108,12 +108,13 @@ object RepoManager { try { ItemCache.ReloadProgressHud.reportProgress("Reloading from Disk", 0, - -1) // TODO: replace with a proper boundy bar + -1) // TODO: replace with a proper bouncy bar ItemCache.ReloadProgressHud.isEnabled = true neuRepo.reload() } catch (exc: NEURepositoryException) { MinecraftClient.getInstance().player?.sendMessage( - Text.literal("Failed to reload repository. This will result in some mod features not working.") + tr("firmament.repo.reloadfail", + "Failed to reload repository. This will result in some mod features not working.") ) ItemCache.ReloadProgressHud.isEnabled = false exc.printStackTrace() diff --git a/src/main/kotlin/util/ErrorUtil.kt b/src/main/kotlin/util/ErrorUtil.kt index 4f229af..afecf25 100644 --- a/src/main/kotlin/util/ErrorUtil.kt +++ b/src/main/kotlin/util/ErrorUtil.kt @@ -7,10 +7,24 @@ object ErrorUtil { Thread.currentThread().stackTrace.any { it.className.startsWith("org.junit.") } || Firmament.DEBUG } + inline fun softCheck(message: String, func: () -> Boolean) { + if (!aggressiveErrors) return + if (func()) return + error(message) + } + @Suppress("NOTHING_TO_INLINE") // Suppressed since i want the logger to not pick up the ErrorUtil stack-frame inline fun softError(message: String) { if (aggressiveErrors) error(message) else Firmament.logger.error(message) } + inline fun notNullOr(nullable: T?, message: String, orElse: () -> T): T { + if (nullable == null) { + softError(message) + return orElse() + } + return nullable + } + } diff --git a/src/main/kotlin/util/filter/IteratorFilterSet.kt b/src/main/kotlin/util/filter/IteratorFilterSet.kt deleted file mode 100644 index 483b8d9..0000000 --- a/src/main/kotlin/util/filter/IteratorFilterSet.kt +++ /dev/null @@ -1,33 +0,0 @@ - -package moe.nea.firmament.util.filter - -abstract class IteratorFilterSet(val original: java.util.Set) : java.util.Set by original { - abstract fun shouldKeepElement(element: K): Boolean - - override fun iterator(): MutableIterator { - val parentIterator = original.iterator() - return object : MutableIterator { - var lastEntry: K? = null - override fun hasNext(): Boolean { - while (lastEntry == null) { - if (!parentIterator.hasNext()) - break - val element = parentIterator.next() - if (!shouldKeepElement(element)) continue - lastEntry = element - } - return lastEntry != null - } - - override fun next(): K { - if (!hasNext()) throw NoSuchElementException() - return lastEntry ?: throw NoSuchElementException() - } - - override fun remove() { - TODO("Not yet implemented") - } - } - } -} - -- cgit