aboutsummaryrefslogtreecommitdiff
path: root/src/main/kotlin/features/texturepack/CustomSkyBlockTextures.kt
blob: b641959cc44217110cf2ff428f199c2fda7206d4 (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
package moe.nea.firmament.features.texturepack

import com.mojang.authlib.minecraft.MinecraftProfileTexture
import com.mojang.authlib.properties.Property
import java.util.Optional
import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable
import kotlin.jvm.optionals.getOrNull
import net.minecraft.block.SkullBlock
import net.minecraft.client.MinecraftClient
import net.minecraft.client.render.RenderLayer
import net.minecraft.client.util.ModelIdentifier
import net.minecraft.component.type.ProfileComponent
import net.minecraft.util.Identifier
import moe.nea.firmament.annotations.Subscribe
import moe.nea.firmament.events.BakeExtraModelsEvent
import moe.nea.firmament.events.CustomItemModelEvent
import moe.nea.firmament.events.FinalizeResourceManagerEvent
import moe.nea.firmament.events.TickEvent
import moe.nea.firmament.features.FirmamentFeature
import moe.nea.firmament.gui.config.ManagedConfig
import moe.nea.firmament.util.collections.WeakCache
import moe.nea.firmament.util.mc.decodeProfileTextureProperty
import moe.nea.firmament.util.skyBlockId

object CustomSkyBlockTextures : FirmamentFeature {
    override val identifier: String
        get() = "custom-skyblock-textures"

    object TConfig : ManagedConfig(identifier, Category.INTEGRATIONS) { // TODO: should this be its own thing?
        val enabled by toggle("enabled") { true }
        val skullsEnabled by toggle("skulls-enabled") { true }
        val cacheForever by toggle("cache-forever") { true }
        val cacheDuration by integer("cache-duration", 0, 100) { 1 }
        val enableModelOverrides by toggle("model-overrides") { true }
        val enableArmorOverrides by toggle("armor-overrides") { true }
        val enableBlockOverrides by toggle("block-overrides") { true }
        val enableLegacyCIT by toggle("legacy-cit") { true }
    }

    override val config: ManagedConfig
        get() = TConfig

    val allItemCaches by lazy {
        listOf(
            CustomItemModelEvent.cache.cache,
            skullTextureCache.cache,
            CustomGlobalTextures.overrideCache.cache,
            CustomGlobalArmorOverrides.overrideCache.cache
        )
    }

    fun clearAllCaches() {
        allItemCaches.forEach(WeakCache<*, *, *>::clear)
    }

    @Subscribe
    fun onTick(it: TickEvent) {
        if (TConfig.cacheForever) return
        if (TConfig.cacheDuration < 1 || it.tickCount % TConfig.cacheDuration == 0) {
            clearAllCaches()
        }
    }

    @Subscribe
    fun onStart(event: FinalizeResourceManagerEvent) {
        event.registerOnApply("Clear firmament CIT caches") {
            clearAllCaches()
        }
    }

    @Subscribe
    fun bakeCustomFirmModels(event: BakeExtraModelsEvent) {
        val resources =
            MinecraftClient.getInstance().resourceManager.findResources("models/item"
            ) { it: Identifier ->
                "firmskyblock" == it.namespace && it.path
                    .endsWith(".json")
            }
        for (identifier in resources.keys) {
            val modelId = ModelIdentifier.ofInventoryVariant(
                Identifier.of(
                    "firmskyblock",
                    identifier.path.substring(
                        "models/item/".length,
                        identifier.path.length - ".json".length),
                ))
            event.addItemModel(modelId)
        }
    }

    @Subscribe
    fun onCustomModelId(it: CustomItemModelEvent) {
        if (!TConfig.enabled) return
        val id = it.itemStack.skyBlockId ?: return
        it.overrideModel = ModelIdentifier.ofInventoryVariant(Identifier.of("firmskyblock", id.identifier.path))
    }

    private val skullTextureCache =
        WeakCache.memoize<ProfileComponent, Optional<Identifier>>("SkullTextureCache") { component ->
            val id = getSkullTexture(component) ?: return@memoize Optional.empty()
            if (!MinecraftClient.getInstance().resourceManager.getResource(id).isPresent) {
                return@memoize Optional.empty()
            }
            return@memoize Optional.of(id)
        }

    private val mcUrlRegex = "https?://textures.minecraft.net/texture/([a-fA-F0-9]+)".toRegex()

    fun getSkullId(textureProperty: Property): String? {
        val texture = decodeProfileTextureProperty(textureProperty) ?: return null
        val textureUrl =
            texture.textures[MinecraftProfileTexture.Type.SKIN]?.url ?: return null
        val mcUrlData = mcUrlRegex.matchEntire(textureUrl) ?: return null
        return mcUrlData.groupValues[1]
    }

    fun getSkullTexture(profile: ProfileComponent): Identifier? {
        val id = getSkullId(profile.properties["textures"].firstOrNull() ?: return null) ?: return null
        return Identifier.of("firmskyblock", "textures/placedskull/$id.png")
    }

    fun modifySkullTexture(
        type: SkullBlock.SkullType?,
        component: ProfileComponent?,
        cir: CallbackInfoReturnable<RenderLayer>
    ) {
        if (type != SkullBlock.Type.PLAYER) return
        if (!TConfig.skullsEnabled) return
        if (component == null) return

        val n = skullTextureCache.invoke(component).getOrNull() ?: return
        cir.returnValue = RenderLayer.getEntityTranslucent(n)
    }
}