diff options
| author | Linnea Gräf <nea@nea.moe> | 2024-08-28 19:04:24 +0200 |
|---|---|---|
| committer | Linnea Gräf <nea@nea.moe> | 2024-08-28 19:04:24 +0200 |
| commit | d2f240ff0ca0d27f417f837e706c781a98c31311 (patch) | |
| tree | 0db7aff6cc14deaf36eed83889d59fd6b3a6f599 /src/main/kotlin/features/texturepack | |
| parent | a6906308163aa3b2d18fa1dc1aa71ac9bbcc83ab (diff) | |
| download | Firmament-d2f240ff0ca0d27f417f837e706c781a98c31311.tar.gz Firmament-d2f240ff0ca0d27f417f837e706c781a98c31311.tar.bz2 Firmament-d2f240ff0ca0d27f417f837e706c781a98c31311.zip | |
Refactor source layout
Introduce compat source sets and move all kotlin sources to the main directory
[no changelog]
Diffstat (limited to 'src/main/kotlin/features/texturepack')
24 files changed, 1672 insertions, 0 deletions
diff --git a/src/main/kotlin/features/texturepack/AlwaysPredicate.kt b/src/main/kotlin/features/texturepack/AlwaysPredicate.kt new file mode 100644 index 0000000..4dd28df --- /dev/null +++ b/src/main/kotlin/features/texturepack/AlwaysPredicate.kt @@ -0,0 +1,17 @@ + +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 new file mode 100644 index 0000000..55a4f32 --- /dev/null +++ b/src/main/kotlin/features/texturepack/AndPredicate.kt @@ -0,0 +1,26 @@ + +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>) : 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 new file mode 100644 index 0000000..ae1f6d5 --- /dev/null +++ b/src/main/kotlin/features/texturepack/BakedModelExtra.kt @@ -0,0 +1,9 @@ + +package moe.nea.firmament.features.texturepack + +import net.minecraft.client.render.model.BakedModel + +interface BakedModelExtra { + fun getHeadModel_firmament(): BakedModel? + fun setHeadModel_firmament(headModel: BakedModel?) +} diff --git a/src/main/kotlin/features/texturepack/BakedOverrideData.kt b/src/main/kotlin/features/texturepack/BakedOverrideData.kt new file mode 100644 index 0000000..c012883 --- /dev/null +++ b/src/main/kotlin/features/texturepack/BakedOverrideData.kt @@ -0,0 +1,8 @@ + +package moe.nea.firmament.features.texturepack + +interface BakedOverrideData { + fun getFirmamentOverrides(): Array<FirmamentModelPredicate>? + fun setFirmamentOverrides(overrides: Array<FirmamentModelPredicate>?) + +} diff --git a/src/main/kotlin/features/texturepack/CustomBlockTextures.kt b/src/main/kotlin/features/texturepack/CustomBlockTextures.kt new file mode 100644 index 0000000..0f2c2e6 --- /dev/null +++ b/src/main/kotlin/features/texturepack/CustomBlockTextures.kt @@ -0,0 +1,295 @@ +@file:UseSerializers(BlockPosSerializer::class, IdentifierSerializer::class) + +package moe.nea.firmament.features.texturepack + +import java.util.concurrent.CompletableFuture +import net.fabricmc.loader.api.FabricLoader +import kotlinx.serialization.ExperimentalSerializationApi +import kotlinx.serialization.KSerializer +import kotlinx.serialization.Serializable +import kotlinx.serialization.Transient +import kotlinx.serialization.UseSerializers +import kotlinx.serialization.descriptors.SerialDescriptor +import kotlinx.serialization.encoding.Decoder +import kotlinx.serialization.encoding.Encoder +import kotlinx.serialization.json.JsonDecoder +import kotlinx.serialization.json.JsonElement +import kotlinx.serialization.json.JsonPrimitive +import kotlinx.serialization.serializer +import kotlin.jvm.optionals.getOrNull +import net.minecraft.block.Block +import net.minecraft.block.BlockState +import net.minecraft.client.render.model.BakedModel +import net.minecraft.client.util.ModelIdentifier +import net.minecraft.registry.RegistryKey +import net.minecraft.registry.RegistryKeys +import net.minecraft.resource.ResourceManager +import net.minecraft.resource.SinglePreparationResourceReloader +import net.minecraft.util.Identifier +import net.minecraft.util.math.BlockPos +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.EarlyResourceReloadEvent +import moe.nea.firmament.events.FinalizeResourceManagerEvent +import moe.nea.firmament.events.SkyblockServerUpdateEvent +import moe.nea.firmament.features.texturepack.CustomGlobalTextures.logger +import moe.nea.firmament.util.IdentifierSerializer +import moe.nea.firmament.util.MC +import moe.nea.firmament.util.SBData +import moe.nea.firmament.util.SkyBlockIsland +import moe.nea.firmament.util.json.BlockPosSerializer +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>, + ) + + @Serializable(with = Replacement.Serializer::class) + data class Replacement( + val block: Identifier, + val sound: Identifier?, + ) { + + @Transient + val blockModelIdentifier get() = ModelIdentifier(block.withPrefixedPath("block/"), "firmament") + + @Transient + val bakedModel: BakedModel by lazy(LazyThreadSafetyMode.NONE) { + MC.instance.bakedModelManager.getModel(blockModelIdentifier) + } + + @OptIn(ExperimentalSerializationApi::class) + @kotlinx.serialization.Serializer(Replacement::class) + object DefaultSerializer : KSerializer<Replacement> + + object Serializer : KSerializer<Replacement> { + val delegate = serializer<JsonElement>() + override val descriptor: SerialDescriptor + get() = delegate.descriptor + + override fun deserialize(decoder: Decoder): Replacement { + val jsonElement = decoder.decodeSerializableValue(delegate) + if (jsonElement is JsonPrimitive) { + require(jsonElement.isString) + return Replacement(Identifier.tryParse(jsonElement.content)!!, null) + } + return (decoder as JsonDecoder).json.decodeFromJsonElement(DefaultSerializer, jsonElement) + } + + override fun serialize(encoder: Encoder, value: Replacement) { + encoder.encodeSerializableValue(DefaultSerializer, value) + } + } + } + + @Serializable + data class Area( + val min: BlockPos, + val max: BlockPos, + ) { + @Transient + val realMin = BlockPos( + minOf(min.x, max.x), + minOf(min.y, max.y), + minOf(min.z, max.z), + ) + + @Transient + val realMax = BlockPos( + maxOf(min.x, max.x), + maxOf(min.y, max.y), + maxOf(min.z, max.z), + ) + + fun roughJoin(other: Area): Area { + return Area( + BlockPos( + minOf(realMin.x, other.realMin.x), + minOf(realMin.y, other.realMin.y), + minOf(realMin.z, other.realMin.z), + ), + BlockPos( + maxOf(realMax.x, other.realMax.x), + maxOf(realMax.y, other.realMax.y), + maxOf(realMax.z, other.realMax.z), + ) + ) + } + + fun contains(blockPos: BlockPos): Boolean { + return (blockPos.x in realMin.x..realMax.x) && + (blockPos.y in realMin.y..realMax.y) && + (blockPos.z in realMin.z..realMax.z) + } + } + + data class LocationReplacements( + val lookup: Map<Block, List<BlockReplacement>> + ) + + data class BlockReplacement( + val checks: List<Area>?, + val replacement: Replacement, + ) { + val roughCheck by lazy(LazyThreadSafetyMode.NONE) { + if (checks == null || checks.size < 3) return@lazy null + checks.reduce { acc, next -> acc.roughJoin(next) } + } + } + + data class BakedReplacements(val data: Map<SkyBlockIsland, LocationReplacements>) + + var allLocationReplacements: BakedReplacements = BakedReplacements(mapOf()) + var currentIslandReplacements: LocationReplacements? = null + + fun refreshReplacements() { + val location = SBData.skyblockLocation + val replacements = + if (CustomSkyBlockTextures.TConfig.enableBlockOverrides) location?.let(allLocationReplacements.data::get) + else null + val lastReplacements = currentIslandReplacements + currentIslandReplacements = replacements + if (lastReplacements != replacements) { + MC.nextTick { + MC.worldRenderer.chunks?.chunks?.forEach { + // false schedules rebuilds outside a 27 block radius to happen async + it.scheduleRebuild(false) + } + sodiumReloadTask?.run() + } + } + } + + private val sodiumReloadTask = runCatching { + Class.forName("moe.nea.firmament.compat.sodium.SodiumChunkReloader").getConstructor().newInstance() as Runnable + }.getOrElse { + if (FabricLoader.getInstance().isModLoaded("sodium")) + logger.error("Could not create sodium chunk reloader") + null + } + + + fun matchesPosition(replacement: BlockReplacement, blockPos: BlockPos?): Boolean { + if (blockPos == null) return true + val rc = replacement.roughCheck + if (rc != null && !rc.contains(blockPos)) return false + val areas = replacement.checks + if (areas != null && !areas.any { it.contains(blockPos) }) return false + return true + } + + @JvmStatic + fun getReplacementModel(block: BlockState, blockPos: BlockPos?): BakedModel? { + return getReplacement(block, blockPos)?.bakedModel + } + + @JvmStatic + fun getReplacement(block: BlockState, blockPos: BlockPos?): Replacement? { + if (isInFallback() && blockPos == null) return null + val replacements = currentIslandReplacements?.lookup?.get(block.block) ?: return null + for (replacement in replacements) { + if (replacement.checks == null || matchesPosition(replacement, blockPos)) + return replacement.replacement + } + return null + } + + + @Subscribe + fun onLocation(event: SkyblockServerUpdateEvent) { + refreshReplacements() + } + + @Volatile + var preparationFuture: CompletableFuture<BakedReplacements> = CompletableFuture.completedFuture(BakedReplacements( + mapOf())) + + val insideFallbackCall = ThreadLocal.withInitial { 0 } + + @JvmStatic + fun enterFallbackCall() { + insideFallbackCall.set(insideFallbackCall.get() + 1) + } + + fun isInFallback() = insideFallbackCall.get() > 0 + + @JvmStatic + fun exitFallbackCall() { + insideFallbackCall.set(insideFallbackCall.get() - 1) + } + + @Subscribe + fun onEarlyReload(event: EarlyResourceReloadEvent) { + preparationFuture = CompletableFuture + .supplyAsync( + { prepare(event.resourceManager) }, event.preparationExecutor) + } + + @Subscribe + fun bakeExtraModels(event: BakeExtraModelsEvent) { + preparationFuture.join().data.values + .flatMap { it.lookup.values } + .flatten() + .mapTo(mutableSetOf()) { it.replacement.blockModelIdentifier } + .forEach { event.addNonItemModel(it) } + } + + private fun prepare(manager: ResourceManager): BakedReplacements { + val resources = manager.findResources("overrides/blocks") { + it.namespace == "firmskyblock" && it.path.endsWith(".json") + } + val map = mutableMapOf<SkyBlockIsland, MutableMap<Block, MutableList<BlockReplacement>>>() + for ((file, resource) in resources) { + val json = + Firmament.tryDecodeJsonFromStream<CustomBlockOverride>(resource.inputStream) + .getOrElse { ex -> + logger.error("Failed to load block texture override at $file", ex) + continue + } + for (mode in json.modes) { + val island = SkyBlockIsland.forMode(mode) + val islandMpa = map.getOrPut(island, ::mutableMapOf) + for ((blockId, replacement) in json.replacements) { + val block = MC.defaultRegistries.getWrapperOrThrow(RegistryKeys.BLOCK) + .getOptional(RegistryKey.of(RegistryKeys.BLOCK, blockId)) + .getOrNull() + if (block == null) { + logger.error("Failed to load block texture override at ${file}: unknown block '$blockId'") + continue + } + val replacements = islandMpa.getOrPut(block.value(), ::mutableListOf) + replacements.add(BlockReplacement(json.area, replacement)) + } + } + } + + return BakedReplacements(map.mapValues { LocationReplacements(it.value) }) + } + + @JvmStatic + fun patchIndigo(orig: BakedModel, pos: BlockPos, state: BlockState): BakedModel { + return getReplacementModel(state, pos) ?: orig + } + + @Subscribe + fun onStart(event: FinalizeResourceManagerEvent) { + event.resourceManager.registerReloader(object : + SinglePreparationResourceReloader<BakedReplacements>() { + override fun prepare(manager: ResourceManager, profiler: Profiler): BakedReplacements { + return preparationFuture.join() + } + + override fun apply(prepared: BakedReplacements, manager: ResourceManager, profiler: Profiler?) { + allLocationReplacements = prepared + refreshReplacements() + } + }) + } +} diff --git a/src/main/kotlin/features/texturepack/CustomGlobalArmorOverrides.kt b/src/main/kotlin/features/texturepack/CustomGlobalArmorOverrides.kt new file mode 100644 index 0000000..23577ee --- /dev/null +++ b/src/main/kotlin/features/texturepack/CustomGlobalArmorOverrides.kt @@ -0,0 +1,106 @@ + +@file:UseSerializers(IdentifierSerializer::class) + +package moe.nea.firmament.features.texturepack + +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable +import kotlinx.serialization.Transient +import kotlinx.serialization.UseSerializers +import net.minecraft.item.ArmorMaterial +import net.minecraft.item.ItemStack +import net.minecraft.resource.ResourceManager +import net.minecraft.resource.SinglePreparationResourceReloader +import net.minecraft.util.Identifier +import net.minecraft.util.profiler.Profiler +import moe.nea.firmament.Firmament +import moe.nea.firmament.annotations.Subscribe +import moe.nea.firmament.events.FinalizeResourceManagerEvent +import moe.nea.firmament.events.subscription.SubscriptionOwner +import moe.nea.firmament.features.FirmamentFeature +import moe.nea.firmament.features.texturepack.CustomGlobalTextures.logger +import moe.nea.firmament.util.IdentifierSerializer +import moe.nea.firmament.util.IdentityCharacteristics +import moe.nea.firmament.util.computeNullableFunction +import moe.nea.firmament.util.skyBlockId + +object CustomGlobalArmorOverrides : SubscriptionOwner { + @Serializable + data class ArmorOverride( + @SerialName("item_ids") + val itemIds: List<String>, + val layers: List<ArmorOverrideLayer>, + val overrides: List<ArmorOverrideOverride> = listOf(), + ) { + @Transient + val bakedLayers = bakeLayers(layers) + } + + fun bakeLayers(layers: List<ArmorOverrideLayer>): List<ArmorMaterial.Layer> { + return layers.map { ArmorMaterial.Layer(it.identifier, it.suffix, it.tint) } + } + + @Serializable + data class ArmorOverrideLayer( + val tint: Boolean = false, + val identifier: Identifier, + val suffix: String = "", + ) + + @Serializable + data class ArmorOverrideOverride( + val predicate: FirmamentModelPredicate, + val layers: List<ArmorOverrideLayer>, + ) { + @Transient + val bakedLayers = bakeLayers(layers) + } + + override val delegateFeature: FirmamentFeature + get() = CustomSkyBlockTextures + + val overrideCache = mutableMapOf<IdentityCharacteristics<ItemStack>, Any>() + + @JvmStatic + fun overrideArmor(stack: ItemStack): List<ArmorMaterial.Layer>? { + if (!CustomSkyBlockTextures.TConfig.enableArmorOverrides) return null + return overrideCache.computeNullableFunction(IdentityCharacteristics(stack)) { + val id = stack.skyBlockId ?: return@computeNullableFunction null + val override = overrides[id.neuItem] ?: return@computeNullableFunction null + for (suboverride in override.overrides) { + if (suboverride.predicate.test(stack)) { + return@computeNullableFunction suboverride.bakedLayers + } + } + return@computeNullableFunction override.bakedLayers + } + } + + var overrides: Map<String, ArmorOverride> = mapOf() + + @Subscribe + fun onStart(event: FinalizeResourceManagerEvent) { + event.resourceManager.registerReloader(object : + SinglePreparationResourceReloader<Map<String, ArmorOverride>>() { + override fun prepare(manager: ResourceManager, profiler: Profiler): Map<String, ArmorOverride> { + val overrideFiles = manager.findResources("overrides/armor_models") { + it.namespace == "firmskyblock" && it.path.endsWith(".json") + } + val overrides = overrideFiles.mapNotNull { + Firmament.tryDecodeJsonFromStream<ArmorOverride>(it.value.inputStream).getOrElse { ex -> + logger.error("Failed to load armor texture override at ${it.key}", ex) + null + } + } + val associatedMap = overrides.flatMap { obj -> obj.itemIds.map { it to obj } } + .toMap() + return associatedMap + } + + override fun apply(prepared: Map<String, ArmorOverride>, manager: ResourceManager, profiler: Profiler) { + overrides = prepared + } + }) + } + +} diff --git a/src/main/kotlin/features/texturepack/CustomGlobalTextures.kt b/src/main/kotlin/features/texturepack/CustomGlobalTextures.kt new file mode 100644 index 0000000..d64c844 --- /dev/null +++ b/src/main/kotlin/features/texturepack/CustomGlobalTextures.kt @@ -0,0 +1,167 @@ + +@file:UseSerializers(IdentifierSerializer::class, CustomModelOverrideParser.FirmamentRootPredicateSerializer::class) + +package moe.nea.firmament.features.texturepack + + +import java.util.concurrent.CompletableFuture +import org.slf4j.LoggerFactory +import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable +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 +import net.minecraft.util.Identifier +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.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.IdentifierSerializer +import moe.nea.firmament.util.IdentityCharacteristics +import moe.nea.firmament.util.MC +import moe.nea.firmament.util.computeNullableFunction +import moe.nea.firmament.util.json.SingletonSerializableList +import moe.nea.firmament.util.runNull + +object CustomGlobalTextures : SinglePreparationResourceReloader<CustomGlobalTextures.CustomGuiTextureOverride>(), + SubscriptionOwner { + override val delegateFeature: FirmamentFeature + get() = CustomSkyBlockTextures + + class CustomGuiTextureOverride( + val classes: List<ItemOverrideCollection> + ) + + @Serializable + data class GlobalItemOverride( + val screen: @Serializable(SingletonSerializableList::class) List<Identifier>, + val model: Identifier, + val predicate: FirmamentModelPredicate, + ) + + @Serializable + data class ScreenFilter( + val title: StringMatcher, + ) + + data class ItemOverrideCollection( + val screenFilter: ScreenFilter, + val overrides: List<GlobalItemOverride>, + ) + + @Subscribe + fun onStart(event: FinalizeResourceManagerEvent) { + MC.resourceManager.registerReloader(this) + } + + @Subscribe + fun onEarlyReload(event: EarlyResourceReloadEvent) { + preparationFuture = CompletableFuture + .supplyAsync( + { + prepare(event.resourceManager) + }, event.preparationExecutor) + } + + @Subscribe + fun onBakeModels(event: BakeExtraModelsEvent) { + for (guiClassOverride in preparationFuture.join().classes) { + for (override in guiClassOverride.overrides) { + event.addItemModel(ModelIdentifier(override.model, "inventory")) + } + } + } + + @Volatile + var preparationFuture: CompletableFuture<CustomGuiTextureOverride> = CompletableFuture.completedFuture( + CustomGuiTextureOverride(listOf())) + + override fun prepare(manager: ResourceManager?, profiler: Profiler?): CustomGuiTextureOverride { + return preparationFuture.join() + } + + override fun apply(prepared: CustomGuiTextureOverride, manager: ResourceManager?, profiler: Profiler?) { + this.guiClassOverrides = prepared + } + + val logger = LoggerFactory.getLogger(CustomGlobalTextures::class.java) + fun prepare(manager: ResourceManager): CustomGuiTextureOverride { + val overrideResources = + 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) + null + } + } + + val byGuiClass = overrideResources.flatMap { override -> override.screen.toSet().map { it to override } } + .groupBy { it.first } + val guiClasses = byGuiClass.entries + .mapNotNull { + val key = it.key + val guiClassResource = + manager.getResource(Identifier.of(key.namespace, "filters/screen/${key.path}.json")) + .getOrNull() + ?: return@mapNotNull runNull { + logger.error("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) + return@mapNotNull null + } + ItemOverrideCollection(screenFilter, it.value.map { it.second }) + } + logger.info("Loaded ${overrideResources.size} global item overrides") + return CustomGuiTextureOverride(guiClasses) + } + + var guiClassOverrides = CustomGuiTextureOverride(listOf()) + + var matchingOverrides: Set<ItemOverrideCollection> = setOf() + + @Subscribe + fun onOpenGui(event: ScreenChangeEvent) { + val newTitle = event.new?.title ?: Text.empty() + matchingOverrides = guiClassOverrides.classes + .filterTo(mutableSetOf()) { it.screenFilter.title.matches(newTitle) } + } + + val overrideCache = mutableMapOf<IdentityCharacteristics<ItemStack>, Any>() + + @JvmStatic + fun replaceGlobalModel( + models: ItemModels, + stack: ItemStack, + cir: CallbackInfoReturnable<BakedModel> + ) { + val value = overrideCache.computeNullableFunction(IdentityCharacteristics(stack)) { + for (guiClassOverride in matchingOverrides) { + for (override in guiClassOverride.overrides) { + if (override.predicate.test(stack)) { + return@computeNullableFunction models.modelManager.getModel( + ModelIdentifier(override.model, "inventory")) + } + } + } + null + } + if (value != null) + cir.returnValue = value + } + + +} diff --git a/src/main/kotlin/features/texturepack/CustomModelOverrideParser.kt b/src/main/kotlin/features/texturepack/CustomModelOverrideParser.kt new file mode 100644 index 0000000..a4e7c02 --- /dev/null +++ b/src/main/kotlin/features/texturepack/CustomModelOverrideParser.kt @@ -0,0 +1,74 @@ + +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 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()) { + |
