aboutsummaryrefslogtreecommitdiff
path: root/src/main/kotlin/features/texturepack/CustomGlobalArmorOverrides.kt
blob: 54e9e11654d5f45dbe3ad0d75e5b13877e32c1eb (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
@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.item.ItemStack
import net.minecraft.item.equipment.EquipmentModel
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.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() {
			modelIdentifier = bakeModel(model, layers)
			overrides.forEach { it.bake() }
		}

		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() {
			modelIdentifier = bakeModel(model, layers)
		}
	}


	val overrideCache = WeakCache.memoize<ItemStack, Optional<Identifier>>("ArmorOverrides") { stack ->
		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 Optional.of(suboverride.modelIdentifier)
			}
		}
		return@memoize Optional.of(override.modelIdentifier)
	}

	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()
				return associatedMap
			}

			override fun apply(prepared: Map<String, ArmorOverride>, manager: ResourceManager, profiler: Profiler) {
				bakedOverrides.clear()
				prepared.forEach { it.value.bake() }
				overrides = prepared
			}
		})
	}

	@JvmStatic
	fun overrideArmor(itemStack: ItemStack): Optional<Identifier> {
		return overrideCache.invoke(itemStack)
	}

	@JvmStatic
	fun overrideArmorLayer(id: Identifier): EquipmentModel? {
		return bakedOverrides[id]
	}

}