aboutsummaryrefslogtreecommitdiff
path: root/src/texturePacks/java/moe
diff options
context:
space:
mode:
authorLinnea Gräf <nea@nea.moe>2024-12-07 13:26:03 +0100
committerLinnea Gräf <nea@nea.moe>2024-12-07 13:26:03 +0100
commitbf7795df22ca7892fae1238403feebb57c005562 (patch)
tree3a49db5bade38498f976321b6e9008203f2d4d3b /src/texturePacks/java/moe
parentcdb5e60f52eea9030af9ca2d4a49913664023965 (diff)
downloadFirmament-bf7795df22ca7892fae1238403feebb57c005562.tar.gz
Firmament-bf7795df22ca7892fae1238403feebb57c005562.tar.bz2
Firmament-bf7795df22ca7892fae1238403feebb57c005562.zip
WIP: Port to compilation on 1.21.4
Diffstat (limited to 'src/texturePacks/java/moe')
-rw-r--r--src/texturePacks/java/moe/nea/firmament/features/texturepack/CustomBlockTextures.kt301
-rw-r--r--src/texturePacks/java/moe/nea/firmament/features/texturepack/CustomGlobalArmorOverrides.kt171
-rw-r--r--src/texturePacks/java/moe/nea/firmament/features/texturepack/CustomGlobalTextures.kt154
-rw-r--r--src/texturePacks/java/moe/nea/firmament/features/texturepack/CustomModelOverrideParser.kt103
-rw-r--r--src/texturePacks/java/moe/nea/firmament/features/texturepack/CustomSkyBlockTextures.kt117
-rw-r--r--src/texturePacks/java/moe/nea/firmament/features/texturepack/CustomTextColors.kt66
-rw-r--r--src/texturePacks/java/moe/nea/firmament/features/texturepack/FirmamentModelPredicate.kt8
-rw-r--r--src/texturePacks/java/moe/nea/firmament/features/texturepack/FirmamentModelPredicateParser.kt8
-rw-r--r--src/texturePacks/java/moe/nea/firmament/features/texturepack/FirmamentRootPredicateSerializer.kt23
-rw-r--r--src/texturePacks/java/moe/nea/firmament/features/texturepack/PredicateModel.kt85
-rw-r--r--src/texturePacks/java/moe/nea/firmament/features/texturepack/RarityMatcher.kt69
-rw-r--r--src/texturePacks/java/moe/nea/firmament/features/texturepack/StringMatcher.kt159
-rw-r--r--src/texturePacks/java/moe/nea/firmament/features/texturepack/TintOverrides.kt75
-rw-r--r--src/texturePacks/java/moe/nea/firmament/features/texturepack/predicates/AlwaysPredicate.kt19
-rw-r--r--src/texturePacks/java/moe/nea/firmament/features/texturepack/predicates/AndPredicate.kt28
-rw-r--r--src/texturePacks/java/moe/nea/firmament/features/texturepack/predicates/DisplayNamePredicate.kt22
-rw-r--r--src/texturePacks/java/moe/nea/firmament/features/texturepack/predicates/ExtraAttributesPredicate.kt271
-rw-r--r--src/texturePacks/java/moe/nea/firmament/features/texturepack/predicates/ItemPredicate.kt34
-rw-r--r--src/texturePacks/java/moe/nea/firmament/features/texturepack/predicates/LorePredicate.kt22
-rw-r--r--src/texturePacks/java/moe/nea/firmament/features/texturepack/predicates/NotPredicate.kt21
-rw-r--r--src/texturePacks/java/moe/nea/firmament/features/texturepack/predicates/NumberMatcher.kt124
-rw-r--r--src/texturePacks/java/moe/nea/firmament/features/texturepack/predicates/OrPredicate.kt29
-rw-r--r--src/texturePacks/java/moe/nea/firmament/features/texturepack/predicates/PetPredicate.kt66
-rw-r--r--src/texturePacks/java/moe/nea/firmament/mixins/custommodels/ApplyHeadModelInItemRenderer.java11
-rw-r--r--src/texturePacks/java/moe/nea/firmament/mixins/custommodels/CustomSkullTexturePatch.java26
-rw-r--r--src/texturePacks/java/moe/nea/firmament/mixins/custommodels/PatchArmorTexture.java30
-rw-r--r--src/texturePacks/java/moe/nea/firmament/mixins/custommodels/PatchLegacyArmorLayerSupport.java23
-rw-r--r--src/texturePacks/java/moe/nea/firmament/mixins/custommodels/ReferenceCustomModelsPatch.java31
-rw-r--r--src/texturePacks/java/moe/nea/firmament/mixins/custommodels/ReplaceBlockBreakSoundPatch.java27
-rw-r--r--src/texturePacks/java/moe/nea/firmament/mixins/custommodels/ReplaceBlockHitSoundPatch.java30
-rw-r--r--src/texturePacks/java/moe/nea/firmament/mixins/custommodels/ReplaceBlockRenderManagerBlockModel.java38
-rw-r--r--src/texturePacks/java/moe/nea/firmament/mixins/custommodels/ReplaceFallbackBlockModel.java21
-rw-r--r--src/texturePacks/java/moe/nea/firmament/mixins/custommodels/ReplaceItemModelPatch.java24
-rw-r--r--src/texturePacks/java/moe/nea/firmament/mixins/custommodels/ReplaceTextColorInHandledScreen.java48
34 files changed, 2284 insertions, 0 deletions
diff --git a/src/texturePacks/java/moe/nea/firmament/features/texturepack/CustomBlockTextures.kt b/src/texturePacks/java/moe/nea/firmament/features/texturepack/CustomBlockTextures.kt
new file mode 100644
index 0000000..e7c379b
--- /dev/null
+++ b/src/texturePacks/java/moe/nea/firmament/features/texturepack/CustomBlockTextures.kt
@@ -0,0 +1,301 @@
+@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 {
+ val r = Class.forName("moe.nea.firmament.compat.sodium.SodiumChunkReloader")
+ .getConstructor()
+ .newInstance() as Runnable
+ r.run()
+ r
+ }.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, it.id) }
+ }
+
+ 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.getOrThrow(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/texturePacks/java/moe/nea/firmament/features/texturepack/CustomGlobalArmorOverrides.kt b/src/texturePacks/java/moe/nea/firmament/features/texturepack/CustomGlobalArmorOverrides.kt
new file mode 100644
index 0000000..84c04af
--- /dev/null
+++ b/src/texturePacks/java/moe/nea/firmament/features/texturepack/CustomGlobalArmorOverrides.kt
@@ -0,0 +1,171 @@
+@file:UseSerializers(IdentifierSerializer::class)
+
+package moe.nea.firmament.features.texturepack
+
+import java.util.Optional
+import java.util.concurrent.atomic.AtomicInteger
+import kotlinx.serialization.SerialName
+import kotlinx.serialization.Serializable
+import kotlinx.serialization.Transient
+import kotlinx.serialization.UseSerializers
+import net.minecraft.client.render.entity.equipment.EquipmentModel
+import net.minecraft.component.type.EquippableComponent
+import net.minecraft.entity.EquipmentSlot
+import net.minecraft.item.ItemStack
+import net.minecraft.item.equipment.EquipmentAssetKeys
+import net.minecraft.registry.RegistryKey
+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.features.texturepack.CustomGlobalTextures.logger
+import moe.nea.firmament.util.IdentifierSerializer
+import moe.nea.firmament.util.collections.WeakCache
+import moe.nea.firmament.util.intoOptional
+import moe.nea.firmament.util.skyBlockId
+
+object CustomGlobalArmorOverrides {
+ @Serializable
+ data class ArmorOverride(
+ @SerialName("item_ids")
+ val itemIds: List<String>,
+ val layers: List<ArmorOverrideLayer>? = null,
+ val model: Identifier? = null,
+ val overrides: List<ArmorOverrideOverride> = listOf(),
+ ) {
+ @Transient
+ lateinit var modelIdentifier: Identifier
+ fun bake(manager: ResourceManager) {
+ modelIdentifier = bakeModel(model, layers)
+ overrides.forEach { it.bake(manager) }
+ }
+
+ init {
+ require(layers != null || model != null) { "Either model or layers must be specified for armor override" }
+ require(layers == null || model == null) { "Can't specify both model and layers for armor override" }
+ }
+ }
+
+ @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>? = null,
+ val model: Identifier? = null,
+ ) {
+ init {
+ require(layers != null || model != null) { "Either model or layers must be specified for armor override override" }
+ require(layers == null || model == null) { "Can't specify both model and layers for armor override override" }
+ }
+
+ @Transient
+ lateinit var modelIdentifier: Identifier
+ fun bake(manager: ResourceManager) {
+ modelIdentifier = bakeModel(model, layers)
+ }
+ }
+
+
+ private fun resolveComponent(slot: EquipmentSlot, model: Identifier): EquippableComponent {
+ return EquippableComponent(
+ slot,
+ null,
+ Optional.of(RegistryKey.of(EquipmentAssetKeys.REGISTRY_KEY, model)),
+ Optional.empty(),
+ Optional.empty(), false, false, false
+ )
+ }
+
+ val overrideCache =
+ WeakCache.memoize<ItemStack, EquipmentSlot, Optional<EquippableComponent>>("ArmorOverrides") { stack, slot ->
+ val id = stack.skyBlockId ?: return@memoize Optional.empty()
+ val override = overrides[id.neuItem] ?: return@memoize Optional.empty()
+ for (suboverride in override.overrides) {
+ if (suboverride.predicate.test(stack)) {
+ return@memoize resolveComponent(slot, suboverride.modelIdentifier).intoOptional()
+ }
+ }
+ return@memoize resolveComponent(slot, override.modelIdentifier).intoOptional()
+ }
+
+ var overrides: Map<String, ArmorOverride> = mapOf()
+ private var bakedOverrides: MutableMap<Identifier, EquipmentModel> = mutableMapOf()
+ private val sentinelFirmRunning = AtomicInteger()
+
+ private fun bakeModel(model: Identifier?, layers: List<ArmorOverrideLayer>?): Identifier {
+ require(model == null || layers == null)
+ if (model != null) {
+ return model
+ } else if (layers != null) {
+ val idNumber = sentinelFirmRunning.incrementAndGet()
+ val identifier = Identifier.of("firmament:sentinel/$idNumber")
+ val equipmentLayers = layers.map {
+ EquipmentModel.Layer(
+ it.identifier, if (it.tint) {
+ Optional.of(EquipmentModel.Dyeable(Optional.empty()))
+ } else {
+ Optional.empty()
+ },
+ false
+ )
+ }
+ bakedOverrides[identifier] = EquipmentModel(
+ mapOf(
+ EquipmentModel.LayerType.HUMANOID to equipmentLayers,
+ EquipmentModel.LayerType.HUMANOID_LEGGINGS to equipmentLayers,
+ )
+ )
+ return identifier
+ } else {
+ error("Either model or layers must be non null")
+ }
+ }
+
+
+ @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()
+ associatedMap.forEach { it.value.bake(manager) }
+ return associatedMap
+ }
+
+ override fun apply(prepared: Map<String, ArmorOverride>, manager: ResourceManager, profiler: Profiler) {
+ bakedOverrides.clear()
+ overrides = prepared
+ }
+ })
+ }
+
+ @JvmStatic
+ fun overrideArmor(itemStack: ItemStack, slot: EquipmentSlot): Optional<EquippableComponent> {
+ return overrideCache.invoke(itemStack, slot)
+ }
+
+ @JvmStatic
+ fun overrideArmorLayer(id: Identifier): EquipmentModel? {
+ return bakedOverrides[id]
+ }
+
+}
diff --git a/src/texturePacks/java/moe/nea/firmament/features/texturepack/CustomGlobalTextures.kt b/src/texturePacks/java/moe/nea/firmament/features/texturepack/CustomGlobalTextures.kt
new file mode 100644
index 0000000..ad44b03
--- /dev/null
+++ b/src/texturePacks/java/moe/nea/firmament/features/texturepack/CustomGlobalTextures.kt
@@ -0,0 +1,154 @@
+@file:UseSerializers(IdentifierSerializer::class, FirmamentRootPredicateSerializer::class)
+
+package moe.nea.firmament.features.texturepack
+
+
+import java.util.concurrent.CompletableFuture
+import org.slf4j.LoggerFactory
+import kotlinx.serialization.Serializable
+import kotlinx.serialization.UseSerializers
+import kotlin.jvm.optionals.getOrNull
+import net.minecraft.client.util.ModelIdentifier
+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.CustomItemModelEvent
+import moe.nea.firmament.events.EarlyResourceReloadEvent
+import moe.nea.firmament.events.FinalizeResourceManagerEvent
+import moe.nea.firmament.events.ScreenChangeEvent
+import moe.nea.firmament.events.subscription.SubscriptionOwner
+import moe.nea.firmament.features.FirmamentFeature
+import moe.nea.firmament.util.ErrorUtil
+import moe.nea.firmament.util.IdentifierSerializer
+import moe.nea.firmament.util.MC
+import moe.nea.firmament.util.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?) {
+ 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 ->
+ ErrorUtil.softError("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 {
+ ErrorUtil.softError("Failed to locate screen filter at $key")
+ }
+ val screenFilter =
+ Firmament.tryDecodeJsonFromStream<ScreenFilter>(guiClassResource.inputStream)
+ .getOrElse { ex ->
+ ErrorUtil.softError("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) }
+ }
+
+ @Subscribe
+ fun replaceGlobalModel(event: CustomItemModelEvent) {
+ val override = matchingOverrides
+ .firstNotNullOfOrNull {
+ it.overrides
+ .asSequence()
+ .filter { it.predicate.test(event.itemStack) }
+ .map { it.model }
+ .firstOrNull()
+ }
+
+ if (override != null)
+ event.overrideIfExists(override)
+ }
+
+
+}
diff --git a/src/texturePacks/java/moe/nea/firmament/features/texturepack/CustomModelOverrideParser.kt b/src/texturePacks/java/moe/nea/firmament/features/texturepack/CustomModelOverrideParser.kt
new file mode 100644
index 0000000..6472993
--- /dev/null
+++ b/src/texturePacks/java/moe/nea/firmament/features/texturepack/CustomModelOverrideParser.kt
@@ -0,0 +1,10