aboutsummaryrefslogtreecommitdiff
path: root/src/main/kotlin/moe/nea/firmament/features/texturepack/CustomGlobalTextures.kt
blob: 24aa6efd5deb3fbf7b3199d219848b8c77abe6cd (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
/*
 * SPDX-FileCopyrightText: 2024 Linnea Gräf <nea@nea.moe>
 *
 * SPDX-License-Identifier: GPL-3.0-or-later
 */

@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.addModel(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
    }


}