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
|
@file:UseSerializers(IdentifierSerializer::class, CustomModelOverrideParser.FirmamentRootPredicateSerializer::class)
package moe.nea.firmament.features.texturepack
import java.util.Optional
import java.util.concurrent.CompletableFuture
import org.slf4j.LoggerFactory
import kotlinx.serialization.Serializable
import kotlinx.serialization.UseSerializers
import kotlin.jvm.optionals.getOrNull
import net.minecraft.client.render.item.ItemModels
import net.minecraft.client.render.model.BakedModel
import net.minecraft.client.util.ModelIdentifier
import net.minecraft.item.ItemStack
import net.minecraft.resource.ResourceManager
import net.minecraft.resource.SinglePreparationResourceReloader
import net.minecraft.text.Text
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.MC
import moe.nea.firmament.util.collections.WeakCache
import moe.nea.firmament.util.intoOptional
import moe.nea.firmament.util.json.SingletonSerializableList
import moe.nea.firmament.util.runNull
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 =
WeakCache.memoize<ItemStack, ItemModels, Optional<BakedModel>>("CustomGlobalTextureModelOverrides") { stack, models ->
matchingOverrides
.firstNotNullOfOrNull {
it.overrides
.asSequence()
.filter { it.predicate.test(stack) }
.map { models.getModel(it.model) }
.firstOrNull()
}
.intoOptional()
}
@JvmStatic
fun replaceGlobalModel(
models: ItemModels,
stack: ItemStack,
): BakedModel? {
return overrideCache.invoke(stack, models).getOrNull()
}
}
|