WIP: Port to compilation on 1.21.4
This commit is contained in:
@@ -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()
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -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]
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,103 @@
|
||||
package moe.nea.firmament.features.texturepack
|
||||
|
||||
import com.google.gson.JsonObject
|
||||
import com.mojang.datafixers.util.Pair
|
||||
import com.mojang.serialization.Codec
|
||||
import com.mojang.serialization.DataResult
|
||||
import com.mojang.serialization.Decoder
|
||||
import com.mojang.serialization.DynamicOps
|
||||
import com.mojang.serialization.Encoder
|
||||
import net.minecraft.client.render.item.model.ItemModelTypes
|
||||
import net.minecraft.item.ItemStack
|
||||
import net.minecraft.util.Identifier
|
||||
import moe.nea.firmament.Firmament
|
||||
import moe.nea.firmament.annotations.Subscribe
|
||||
import moe.nea.firmament.events.FinalizeResourceManagerEvent
|
||||
import moe.nea.firmament.features.texturepack.predicates.AndPredicate
|
||||
import moe.nea.firmament.features.texturepack.predicates.DisplayNamePredicate
|
||||
import moe.nea.firmament.features.texturepack.predicates.ExtraAttributesPredicate
|
||||
import moe.nea.firmament.features.texturepack.predicates.ItemPredicate
|
||||
import moe.nea.firmament.features.texturepack.predicates.LorePredicate
|
||||
import moe.nea.firmament.features.texturepack.predicates.NotPredicate
|
||||
import moe.nea.firmament.features.texturepack.predicates.OrPredicate
|
||||
import moe.nea.firmament.features.texturepack.predicates.PetPredicate
|
||||
import moe.nea.firmament.util.json.KJsonOps
|
||||
|
||||
object CustomModelOverrideParser {
|
||||
|
||||
val LEGACY_CODEC: Codec<FirmamentModelPredicate> =
|
||||
Codec.of(
|
||||
Encoder.error("cannot encode legacy firmament model predicates"),
|
||||
object : Decoder<FirmamentModelPredicate> {
|
||||
override fun <T : Any?> decode(
|
||||
ops: DynamicOps<T>,
|
||||
input: T
|
||||
): DataResult<Pair<FirmamentModelPredicate, T>> {
|
||||
try {
|
||||
val pred = Firmament.json.decodeFromJsonElement(
|
||||
FirmamentRootPredicateSerializer,
|
||||
ops.convertTo(KJsonOps.INSTANCE, input))
|
||||
return DataResult.success(Pair.of(pred, ops.empty()))
|
||||
} catch (ex: Exception) {
|
||||
return DataResult.error { "Could not deserialize ${ex.message}" }
|
||||
}
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
val predicateParsers = mutableMapOf<Identifier, FirmamentModelPredicateParser>()
|
||||
|
||||
|
||||
fun registerPredicateParser(name: String, parser: FirmamentModelPredicateParser) {
|
||||
predicateParsers[Identifier.of("firmament", name)] = parser
|
||||
}
|
||||
|
||||
init {
|
||||
registerPredicateParser("display_name", DisplayNamePredicate.Parser)
|
||||
registerPredicateParser("lore", LorePredicate.Parser)
|
||||
registerPredicateParser("all", AndPredicate.Parser)
|
||||
registerPredicateParser("any", OrPredicate.Parser)
|
||||
registerPredicateParser("not", NotPredicate.Parser)
|
||||
registerPredicateParser("item", ItemPredicate.Parser)
|
||||
registerPredicateParser("extra_attributes", ExtraAttributesPredicate.Parser)
|
||||
registerPredicateParser("pet", PetPredicate.Parser)
|
||||
}
|
||||
|
||||
private val neverPredicate = listOf(
|
||||
object : FirmamentModelPredicate {
|
||||
override fun test(stack: ItemStack): Boolean {
|
||||
return false
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
fun parsePredicates(predicates: JsonObject): List<FirmamentModelPredicate> {
|
||||
val parsedPredicates = mutableListOf<FirmamentModelPredicate>()
|
||||
for (predicateName in predicates.keySet()) {
|
||||
if (!predicateName.startsWith("firmament:")) continue
|
||||
val identifier = Identifier.of(predicateName)
|
||||
val parser = predicateParsers[identifier] ?: return neverPredicate
|
||||
val parsedPredicate = parser.parse(predicates[predicateName]) ?: return neverPredicate
|
||||
parsedPredicates.add(parsedPredicate)
|
||||
}
|
||||
return parsedPredicates
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
fun parseCustomModelOverrides(jsonObject: JsonObject): Array<FirmamentModelPredicate>? {
|
||||
val predicates = (jsonObject["predicate"] as? JsonObject) ?: return null
|
||||
val parsedPredicates = parsePredicates(predicates)
|
||||
if (parsedPredicates.isEmpty())
|
||||
return null
|
||||
return parsedPredicates.toTypedArray()
|
||||
}
|
||||
|
||||
@Subscribe
|
||||
fun finalizeResources(event: FinalizeResourceManagerEvent) {
|
||||
ItemModelTypes.ID_MAPPER.put(
|
||||
Firmament.identifier("predicates/legacy"),
|
||||
PredicateModel.Unbaked.CODEC
|
||||
)
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,117 @@
|
||||
package moe.nea.firmament.features.texturepack
|
||||
|
||||
import com.mojang.authlib.minecraft.MinecraftProfileTexture
|
||||
import com.mojang.authlib.properties.Property
|
||||
import java.util.Optional
|
||||
import org.jetbrains.annotations.Nullable
|
||||
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.component.type.ProfileComponent
|
||||
import net.minecraft.util.Identifier
|
||||
import moe.nea.firmament.annotations.Subscribe
|
||||
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.features.debug.PowerUserTools
|
||||
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 }
|
||||
val allowRecoloringUiText by toggle("recolor-text") { true }
|
||||
}
|
||||
|
||||
override val config: ManagedConfig
|
||||
get() = TConfig
|
||||
|
||||
val allItemCaches by lazy {
|
||||
listOf(
|
||||
skullTextureCache.cache,
|
||||
CustomGlobalArmorOverrides.overrideCache.cache
|
||||
)
|
||||
}
|
||||
|
||||
init {
|
||||
PowerUserTools.getSkullId = ::getSkullTexture
|
||||
}
|
||||
|
||||
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 onCustomModelId(it: CustomItemModelEvent) {
|
||||
if (!TConfig.enabled) return
|
||||
val id = it.itemStack.skyBlockId ?: return
|
||||
it.overrideIfExists(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)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,66 @@
|
||||
package moe.nea.firmament.features.texturepack
|
||||
|
||||
import java.util.Optional
|
||||
import kotlinx.serialization.Serializable
|
||||
import kotlin.jvm.optionals.getOrNull
|
||||
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.FinalizeResourceManagerEvent
|
||||
import moe.nea.firmament.util.collections.WeakCache
|
||||
|
||||
object CustomTextColors : SinglePreparationResourceReloader<CustomTextColors.TextOverrides?>() {
|
||||
@Serializable
|
||||
data class TextOverrides(
|
||||
val defaultColor: Int,
|
||||
val overrides: List<TextOverride> = listOf()
|
||||
)
|
||||
|
||||
@Serializable
|
||||
data class TextOverride(
|
||||
val predicate: StringMatcher,
|
||||
val override: Int,
|
||||
)
|
||||
|
||||
@Subscribe
|
||||
fun registerTextColorReloader(event: FinalizeResourceManagerEvent) {
|
||||
event.resourceManager.registerReloader(this)
|
||||
}
|
||||
|
||||
val cache = WeakCache.memoize<Text, Optional<Int>>("CustomTextColor") { text ->
|
||||
val override = textOverrides ?: return@memoize Optional.empty()
|
||||
Optional.of(override.overrides.find { it.predicate.matches(text) }?.override ?: override.defaultColor)
|
||||
}
|
||||
|
||||
fun mapTextColor(text: Text, oldColor: Int): Int {
|
||||
if (textOverrides == null) return oldColor
|
||||
return cache(text).getOrNull() ?: oldColor
|
||||
}
|
||||
|
||||
override fun prepare(
|
||||
manager: ResourceManager,
|
||||
profiler: Profiler
|
||||
): TextOverrides? {
|
||||
val resource = manager.getResource(Identifier.of("firmskyblock", "overrides/text_colors.json")).getOrNull()
|
||||
?: return null
|
||||
return Firmament.tryDecodeJsonFromStream<TextOverrides>(resource.inputStream)
|
||||
.getOrElse {
|
||||
Firmament.logger.error("Could not parse text_colors.json", it)
|
||||
null
|
||||
}
|
||||
}
|
||||
|
||||
var textOverrides: TextOverrides? = null
|
||||
|
||||
override fun apply(
|
||||
prepared: TextOverrides?,
|
||||
manager: ResourceManager,
|
||||
profiler: Profiler
|
||||
) {
|
||||
textOverrides = prepared
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
|
||||
package moe.nea.firmament.features.texturepack
|
||||
|
||||
import net.minecraft.item.ItemStack
|
||||
|
||||
interface FirmamentModelPredicate {
|
||||
fun test(stack: ItemStack): Boolean
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
|
||||
package moe.nea.firmament.features.texturepack
|
||||
|
||||
import com.google.gson.JsonElement
|
||||
|
||||
interface FirmamentModelPredicateParser {
|
||||
fun parse(jsonElement: JsonElement): FirmamentModelPredicate?
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
package moe.nea.firmament.features.texturepack
|
||||
|
||||
import com.google.gson.JsonObject
|
||||
import kotlinx.serialization.KSerializer
|
||||
import kotlinx.serialization.descriptors.SerialDescriptor
|
||||
import kotlinx.serialization.encoding.Decoder
|
||||
import kotlinx.serialization.encoding.Encoder
|
||||
import moe.nea.firmament.features.texturepack.predicates.AndPredicate
|
||||
|
||||
object FirmamentRootPredicateSerializer : KSerializer<FirmamentModelPredicate> {
|
||||
val delegateSerializer = kotlinx.serialization.json.JsonObject.serializer()
|
||||
override val descriptor: SerialDescriptor
|
||||
get() = SerialDescriptor("FirmamentModelRootPredicate", delegateSerializer.descriptor)
|
||||
|
||||
override fun deserialize(decoder: Decoder): FirmamentModelPredicate {
|
||||
val json = decoder.decodeSerializableValue(delegateSerializer).intoGson() as JsonObject
|
||||
return AndPredicate(CustomModelOverrideParser.parsePredicates(json).toTypedArray())
|
||||
}
|
||||
|
||||
override fun serialize(encoder: Encoder, value: FirmamentModelPredicate) {
|
||||
TODO("Cannot serialize firmament predicates")
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,85 @@
|
||||
package moe.nea.firmament.features.texturepack
|
||||
|
||||
import com.mojang.serialization.Codec
|
||||
import com.mojang.serialization.MapCodec
|
||||
import com.mojang.serialization.codecs.RecordCodecBuilder
|
||||
import net.minecraft.client.item.ItemModelManager
|
||||
import net.minecraft.client.render.item.ItemRenderState
|
||||
import net.minecraft.client.render.item.model.ItemModel
|
||||
import net.minecraft.client.render.item.model.ItemModelTypes
|
||||
import net.minecraft.client.render.model.ResolvableModel
|
||||
import net.minecraft.client.world.ClientWorld
|
||||
import net.minecraft.entity.LivingEntity
|
||||
import net.minecraft.item.ItemStack
|
||||
import net.minecraft.item.ModelTransformationMode
|
||||
|
||||
class PredicateModel {
|
||||
data class Baked(
|
||||
val fallback: ItemModel,
|
||||
val overrides: List<Override>
|
||||
) : ItemModel {
|
||||
data class Override(
|
||||
val model: ItemModel,
|
||||
val predicate: FirmamentModelPredicate,
|
||||
)
|
||||
|
||||
override fun update(
|
||||
state: ItemRenderState,
|
||||
stack: ItemStack,
|
||||
resolver: ItemModelManager,
|
||||
transformationMode: ModelTransformationMode,
|
||||
world: ClientWorld?,
|
||||
user: LivingEntity?,
|
||||
seed: Int
|
||||
) {
|
||||
val model =
|
||||
overrides
|
||||
.find { it.predicate.test(stack) }
|
||||
?.model
|
||||
?: fallback
|
||||
model.update(state, stack, resolver, transformationMode, world, user, seed)
|
||||
}
|
||||
}
|
||||
|
||||
data class Unbaked(
|
||||
val fallback: ItemModel.Unbaked,
|
||||
val overrides: List<Override>,
|
||||
) : ItemModel.Unbaked {
|
||||
companion object {
|
||||
val OVERRIDE_CODEC: Codec<Override> = RecordCodecBuilder.create {
|
||||
it.group(
|
||||
ItemModelTypes.CODEC.fieldOf("model").forGetter(Override::model),
|
||||
CustomModelOverrideParser.LEGACY_CODEC.fieldOf("predicate").forGetter(Override::predicate),
|
||||
).apply(it, Unbaked::Override)
|
||||
}
|
||||
val CODEC: MapCodec<Unbaked> =
|
||||
RecordCodecBuilder.mapCodec {
|
||||
it.group(
|
||||
ItemModelTypes.CODEC.fieldOf("fallback").forGetter(Unbaked::fallback),
|
||||
OVERRIDE_CODEC.listOf().fieldOf("overrides").forGetter(Unbaked::overrides),
|
||||
).apply(it, ::Unbaked)
|
||||
}
|
||||
}
|
||||
|
||||
data class Override(
|
||||
val model: ItemModel.Unbaked,
|
||||
val predicate: FirmamentModelPredicate,
|
||||
)
|
||||
|
||||
override fun resolve(resolver: ResolvableModel.Resolver) {
|
||||
fallback.resolve(resolver)
|
||||
overrides.forEach { it.model.resolve(resolver) }
|
||||
}
|
||||
|
||||
override fun getCodec(): MapCodec<out Unbaked> {
|
||||
return CODEC
|
||||
}
|
||||
|
||||
override fun bake(context: ItemModel.BakeContext): ItemModel {
|
||||
return Baked(
|
||||
fallback.bake(context),
|
||||
overrides.map { Baked.Override(it.model.bake(context), it.predicate) }
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
|
||||
package moe.nea.firmament.features.texturepack
|
||||
|
||||
import com.google.gson.JsonElement
|
||||
import io.github.moulberry.repo.data.Rarity
|
||||
import moe.nea.firmament.util.useMatch
|
||||
|
||||
abstract class RarityMatcher {
|
||||
abstract fun match(rarity: Rarity): Boolean
|
||||
|
||||
companion object {
|
||||
fun parse(jsonElement: JsonElement): RarityMatcher {
|
||||
val string = jsonElement.asString
|
||||
val range = parseRange(string)
|
||||
if (range != null) return range
|
||||
return Exact(Rarity.valueOf(string))
|
||||
}
|
||||
|
||||
private val allRarities = Rarity.entries.joinToString("|", "(?:", ")")
|
||||
private val intervalSpec =
|
||||
"(?<beginningOpen>[\\[\\(])(?<beginning>$allRarities)?,(?<ending>$allRarities)?(?<endingOpen>[\\]\\)])"
|
||||
.toPattern()
|
||||
|
||||
fun parseRange(string: String): RangeMatcher? {
|
||||
intervalSpec.useMatch<Nothing>(string) {
|
||||
// Open in the set-theory sense, meaning does not include its end.
|
||||
val beginningOpen = group("beginningOpen") == "("
|
||||
val endingOpen = group("endingOpen") == ")"
|
||||
val beginning = group("beginning")?.let(Rarity::valueOf)
|
||||
val ending = group("ending")?.let(Rarity::valueOf)
|
||||
return RangeMatcher(beginning, !beginningOpen, ending, !endingOpen)
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
data class Exact(val expected: Rarity) : RarityMatcher() {
|
||||
override fun match(rarity: Rarity): Boolean {
|
||||
return rarity == expected
|
||||
}
|
||||
}
|
||||
|
||||
data class RangeMatcher(
|
||||
val beginning: Rarity?,
|
||||
val beginningInclusive: Boolean,
|
||||
val ending: Rarity?,
|
||||
val endingInclusive: Boolean,
|
||||
) : RarityMatcher() {
|
||||
override fun match(rarity: Rarity): Boolean {
|
||||
if (beginning != null) {
|
||||
if (beginningInclusive) {
|
||||
if (rarity < beginning) return false
|
||||
} else {
|
||||
if (rarity <= beginning) return false
|
||||
}
|
||||
}
|
||||
if (ending != null) {
|
||||
if (endingInclusive) {
|
||||
if (rarity > ending) return false
|
||||
} else {
|
||||
if (rarity >= ending) return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,159 @@
|
||||
|
||||
package moe.nea.firmament.features.texturepack
|
||||
|
||||
import com.google.gson.JsonArray
|
||||
import com.google.gson.JsonElement
|
||||
import com.google.gson.JsonNull
|
||||
import com.google.gson.JsonObject
|
||||
import com.google.gson.JsonPrimitive
|
||||
import com.google.gson.internal.LazilyParsedNumber
|
||||
import java.util.function.Predicate
|
||||
import kotlinx.serialization.KSerializer
|
||||
import kotlinx.serialization.Serializable
|
||||
import kotlinx.serialization.descriptors.SerialDescriptor
|
||||
import kotlinx.serialization.encoding.Decoder
|
||||
import kotlinx.serialization.encoding.Encoder
|
||||
import net.minecraft.nbt.NbtString
|
||||
import net.minecraft.text.Text
|
||||
import moe.nea.firmament.util.MC
|
||||
import moe.nea.firmament.util.removeColorCodes
|
||||
|
||||
@Serializable(with = StringMatcher.Serializer::class)
|
||||
interface StringMatcher {
|
||||
fun matches(string: String): Boolean
|
||||
fun matches(text: Text): Boolean {
|
||||
return matches(text.string)
|
||||
}
|
||||
|
||||
fun matches(nbt: NbtString): Boolean {
|
||||
val string = nbt.asString()
|
||||
val jsonStart = string.indexOf('{')
|
||||
val stringStart = string.indexOf('"')
|
||||
val isString = stringStart >= 0 && string.subSequence(0, stringStart).isBlank()
|
||||
val isJson = jsonStart >= 0 && string.subSequence(0, jsonStart).isBlank()
|
||||
if (isString || isJson)
|
||||
return matches(Text.Serialization.fromJson(string, MC.defaultRegistries) ?: return false)
|
||||
return matches(string)
|
||||
}
|
||||
|
||||
class Equals(input: String, val stripColorCodes: Boolean) : StringMatcher {
|
||||
private val expected = if (stripColorCodes) input.removeColorCodes() else input
|
||||
override fun matches(string: String): Boolean {
|
||||
return expected == (if (stripColorCodes) string.removeColorCodes() else string)
|
||||
}
|
||||
|
||||
override fun toString(): String {
|
||||
return "Equals($expected, stripColorCodes = $stripColorCodes)"
|
||||
}
|
||||
}
|
||||
|
||||
class Pattern(val patternWithColorCodes: String, val stripColorCodes: Boolean) : StringMatcher {
|
||||
private val regex: Predicate<String> = patternWithColorCodes.toPattern().asMatchPredicate()
|
||||
override fun matches(string: String): Boolean {
|
||||
return regex.test(if (stripColorCodes) string.removeColorCodes() else string)
|
||||
}
|
||||
|
||||
override fun toString(): String {
|
||||
return "Pattern($patternWithColorCodes, stripColorCodes = $stripColorCodes)"
|
||||
}
|
||||
}
|
||||
|
||||
object Serializer : KSerializer<StringMatcher> {
|
||||
val delegateSerializer = kotlinx.serialization.json.JsonElement.serializer()
|
||||
override val descriptor: SerialDescriptor
|
||||
get() = SerialDescriptor("StringMatcher", delegateSerializer.descriptor)
|
||||
|
||||
override fun deserialize(decoder: Decoder): StringMatcher {
|
||||
val delegate = decoder.decodeSerializableValue(delegateSerializer)
|
||||
val gsonDelegate = delegate.intoGson()
|
||||
return parse(gsonDelegate)
|
||||
}
|
||||
|
||||
override fun serialize(encoder: Encoder, value: StringMatcher) {
|
||||
encoder.encodeSerializableValue(delegateSerializer, serialize(value).intoKotlinJson())
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
companion object {
|
||||
fun serialize(stringMatcher: StringMatcher): JsonElement {
|
||||
TODO("Cannot serialize string matchers rn")
|
||||
}
|
||||
|
||||
fun parse(jsonElement: JsonElement): StringMatcher {
|
||||
if (jsonElement is JsonPrimitive) {
|
||||
return Equals(jsonElement.asString, true)
|
||||
}
|
||||
if (jsonElement is JsonObject) {
|
||||
val regex = jsonElement["regex"] as JsonPrimitive?
|
||||
val text = jsonElement["equals"] as JsonPrimitive?
|
||||
val shouldStripColor = when (val color = (jsonElement["color"] as JsonPrimitive?)?.asString) {
|
||||
"preserve" -> false
|
||||
"strip", null -> true
|
||||
else -> error("Unknown color preservation mode: $color")
|
||||
}
|
||||
if ((regex == null) == (text == null)) error("Could not parse $jsonElement as string matcher")
|
||||
if (regex != null)
|
||||
return Pattern(regex.asString, shouldStripColor)
|
||||
if (text != null)
|
||||
return Equals(text.asString, shouldStripColor)
|
||||
}
|
||||
error("Could not parse $jsonElement as a string matcher")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun JsonElement.intoKotlinJson(): kotlinx.serialization.json.JsonElement {
|
||||
when (this) {
|
||||
is JsonNull -> return kotlinx.serialization.json.JsonNull
|
||||
is JsonObject -> {
|
||||
return kotlinx.serialization.json.JsonObject(this.entrySet()
|
||||
.associate { it.key to it.value.intoKotlinJson() })
|
||||
}
|
||||
|
||||
is JsonArray -> {
|
||||
return kotlinx.serialization.json.JsonArray(this.map { it.intoKotlinJson() })
|
||||
}
|
||||
|
||||
is JsonPrimitive -> {
|
||||
if (this.isString)
|
||||
return kotlinx.serialization.json.JsonPrimitive(this.asString)
|
||||
if (this.isBoolean)
|
||||
return kotlinx.serialization.json.JsonPrimitive(this.asBoolean)
|
||||
return kotlinx.serialization.json.JsonPrimitive(this.asNumber)
|
||||
}
|
||||
|
||||
else -> error("Unknown json variant $this")
|
||||
}
|
||||
}
|
||||
|
||||
fun kotlinx.serialization.json.JsonElement.intoGson(): JsonElement {
|
||||
when (this) {
|
||||
is kotlinx.serialization.json.JsonNull -> return JsonNull.INSTANCE
|
||||
is kotlinx.serialization.json.JsonPrimitive -> {
|
||||
if (this.isString)
|
||||
return JsonPrimitive(this.content)
|
||||
if (this.content == "true")
|
||||
return JsonPrimitive(true)
|
||||
if (this.content == "false")
|
||||
return JsonPrimitive(false)
|
||||
return JsonPrimitive(LazilyParsedNumber(this.content))
|
||||
}
|
||||
|
||||
is kotlinx.serialization.json.JsonObject -> {
|
||||
val obj = JsonObject()
|
||||
for ((k, v) in this) {
|
||||
obj.add(k, v.intoGson())
|
||||
}
|
||||
return obj
|
||||
}
|
||||
|
||||
is kotlinx.serialization.json.JsonArray -> {
|
||||
val arr = JsonArray()
|
||||
for (v in this) {
|
||||
arr.add(v.intoGson())
|
||||
}
|
||||
return arr
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,75 @@
|
||||
package moe.nea.firmament.features.texturepack
|
||||
|
||||
import com.google.gson.JsonObject
|
||||
import com.google.gson.JsonPrimitive
|
||||
import moe.nea.firmament.util.ErrorUtil
|
||||
|
||||
data class TintOverrides(
|
||||
val layerMap: Map<Int, TintOverride> = mapOf()
|
||||
) {
|
||||
val hasOverrides by lazy { layerMap.values.any { it !is Reset } }
|
||||
|
||||
companion object {
|
||||
val EMPTY = TintOverrides()
|
||||
private val threadLocal = object : ThreadLocal<TintOverrides>() {}
|
||||
fun enter(overrides: TintOverrides?) {
|
||||
ErrorUtil.softCheck("Double entered tintOverrides",
|
||||
threadLocal.get() == null)
|
||||
threadLocal.set(overrides ?: EMPTY)
|
||||
}
|
||||
|
||||
fun exit(overrides: TintOverrides?) {
|
||||
ErrorUtil.softCheck("Exited with non matching enter tintOverrides",
|
||||
threadLocal.get() == (overrides ?: EMPTY))
|
||||
threadLocal.remove()
|
||||
}
|
||||
|
||||
fun getCurrentOverrides(): TintOverrides {
|
||||
return ErrorUtil.notNullOr(threadLocal.get(), "Got current tintOverrides without entering") {
|
||||
EMPTY
|
||||
}
|
||||
}
|
||||
|
||||
fun parse(jsonObject: JsonObject): TintOverrides {
|
||||
val map = mutableMapOf<Int, TintOverride>()
|
||||
for ((key, value) in jsonObject.entrySet()) {
|
||||
val layerIndex =
|
||||
ErrorUtil.notNullOr(key.toIntOrNull(),
|
||||
"Unknown layer index $value. Should be integer") { continue }
|
||||
if (value.isJsonNull) {
|
||||
map[layerIndex] = Reset
|
||||
continue
|
||||
}
|
||||
val override = (value as? JsonPrimitive)
|
||||
?.takeIf(JsonPrimitive::isNumber)
|
||||
?.asInt
|
||||
?.let(TintOverrides::Fixed)
|
||||
if (override == null) {
|
||||
ErrorUtil.softError("Invalid tint override for a layer: $value")
|
||||
continue
|
||||
}
|
||||
map[layerIndex] = override
|
||||
}
|
||||
return TintOverrides(map)
|
||||
}
|
||||
}
|
||||
|
||||
fun mergeWithParent(parent: TintOverrides): TintOverrides {
|
||||
val mergedMap = parent.layerMap.toMutableMap()
|
||||
mergedMap.putAll(this.layerMap)
|
||||
return TintOverrides(mergedMap)
|
||||
}
|
||||
|
||||
fun hasOverrides(): Boolean = hasOverrides
|
||||
fun getOverride(tintIndex: Int): Int? {
|
||||
return when (val tint = layerMap[tintIndex]) {
|
||||
is Reset -> null
|
||||
is Fixed -> tint.color
|
||||
null -> null
|
||||
}
|
||||
}
|
||||
|
||||
sealed interface TintOverride
|
||||
data object Reset : TintOverride
|
||||
data class Fixed(val color: Int) : TintOverride
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
|
||||
package moe.nea.firmament.features.texturepack.predicates
|
||||
|
||||
import com.google.gson.JsonElement
|
||||
import moe.nea.firmament.features.texturepack.FirmamentModelPredicate
|
||||
import moe.nea.firmament.features.texturepack.FirmamentModelPredicateParser
|
||||
import net.minecraft.item.ItemStack
|
||||
|
||||
object AlwaysPredicate : FirmamentModelPredicate {
|
||||
override fun test(stack: ItemStack): Boolean {
|
||||
return true
|
||||
}
|
||||
|
||||
object Parser : FirmamentModelPredicateParser {
|
||||
override fun parse(jsonElement: JsonElement): FirmamentModelPredicate {
|
||||
return AlwaysPredicate
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
package moe.nea.firmament.features.texturepack.predicates
|
||||
|
||||
import com.google.gson.JsonArray
|
||||
import com.google.gson.JsonElement
|
||||
import com.google.gson.JsonObject
|
||||
import moe.nea.firmament.features.texturepack.CustomModelOverrideParser
|
||||
import moe.nea.firmament.features.texturepack.FirmamentModelPredicate
|
||||
import moe.nea.firmament.features.texturepack.FirmamentModelPredicateParser
|
||||
import net.minecraft.item.ItemStack
|
||||
|
||||
class AndPredicate(val children: Array<FirmamentModelPredicate>) : FirmamentModelPredicate {
|
||||
override fun test(stack: ItemStack): Boolean {
|
||||
return children.all { it.test(stack) }
|
||||
}
|
||||
|
||||
object Parser : FirmamentModelPredicateParser {
|
||||
override fun parse(jsonElement: JsonElement): FirmamentModelPredicate {
|
||||
val children =
|
||||
(jsonElement as JsonArray)
|
||||
.flatMap {
|
||||
CustomModelOverrideParser.parsePredicates(it as JsonObject)
|
||||
}
|
||||
.toTypedArray()
|
||||
return AndPredicate(children)
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
|
||||
package moe.nea.firmament.features.texturepack.predicates
|
||||
|
||||
import com.google.gson.JsonElement
|
||||
import moe.nea.firmament.features.texturepack.FirmamentModelPredicate
|
||||
import moe.nea.firmament.features.texturepack.FirmamentModelPredicateParser
|
||||
import moe.nea.firmament.features.texturepack.StringMatcher
|
||||
import net.minecraft.item.ItemStack
|
||||
import moe.nea.firmament.util.mc.displayNameAccordingToNbt
|
||||
|
||||
data class DisplayNamePredicate(val stringMatcher: StringMatcher) : FirmamentModelPredicate {
|
||||
override fun test(stack: ItemStack): Boolean {
|
||||
val display = stack.displayNameAccordingToNbt
|
||||
return stringMatcher.matches(display)
|
||||
}
|
||||
|
||||
object Parser : FirmamentModelPredicateParser {
|
||||
override fun parse(jsonElement: JsonElement): FirmamentModelPredicate {
|
||||
return DisplayNamePredicate(StringMatcher.parse(jsonElement))
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,271 @@
|
||||
|
||||
package moe.nea.firmament.features.texturepack.predicates
|
||||
|
||||
import com.google.gson.JsonArray
|
||||
import com.google.gson.JsonElement
|
||||
import com.google.gson.JsonObject
|
||||
import com.google.gson.JsonPrimitive
|
||||
import moe.nea.firmament.features.texturepack.FirmamentModelPredicate
|
||||
import moe.nea.firmament.features.texturepack.FirmamentModelPredicateParser
|
||||
import moe.nea.firmament.features.texturepack.StringMatcher
|
||||
import net.minecraft.item.ItemStack
|
||||
import net.minecraft.nbt.NbtByte
|
||||
import net.minecraft.nbt.NbtCompound
|
||||
import net.minecraft.nbt.NbtDouble
|
||||
import net.minecraft.nbt.NbtElement
|
||||
import net.minecraft.nbt.NbtFloat
|
||||
import net.minecraft.nbt.NbtInt
|
||||
import net.minecraft.nbt.NbtList
|
||||
import net.minecraft.nbt.NbtLong
|
||||
import net.minecraft.nbt.NbtShort
|
||||
import net.minecraft.nbt.NbtString
|
||||
import moe.nea.firmament.util.extraAttributes
|
||||
|
||||
fun interface NbtMatcher {
|
||||
fun matches(nbt: NbtElement): Boolean
|
||||
|
||||
object Parser {
|
||||
fun parse(jsonElement: JsonElement): NbtMatcher? {
|
||||
if (jsonElement is JsonPrimitive) {
|
||||
if (jsonElement.isString) {
|
||||
val string = jsonElement.asString
|
||||
return MatchStringExact(string)
|
||||
}
|
||||
if (jsonElement.isNumber) {
|
||||
return MatchNumberExact(jsonElement.asLong) //TODO: parse generic number
|
||||
}
|
||||
}
|
||||
if (jsonElement is JsonObject) {
|
||||
var encounteredParser: NbtMatcher? = null
|
||||
for (entry in ExclusiveParserType.entries) {
|
||||
val data = jsonElement[entry.key] ?: continue
|
||||
if (encounteredParser != null) {
|
||||
// TODO: warn
|
||||
return null
|
||||
}
|
||||
encounteredParser = entry.parse(data) ?: return null
|
||||
}
|
||||
return encounteredParser
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
enum class ExclusiveParserType(val key: String) {
|
||||
STRING("string") {
|
||||
override fun parse(element: JsonElement): NbtMatcher? {
|
||||
return MatchString(StringMatcher.parse(element))
|
||||
}
|
||||
},
|
||||
INT("int") {
|
||||
override fun parse(element: JsonElement): NbtMatcher? {
|
||||
return parseGenericNumber(element,
|
||||
{ it.asInt },
|
||||
{ (it as? NbtInt)?.intValue() },
|
||||
{ a, b ->
|
||||
if (a == b) Comparison.EQUAL
|
||||
else if (a < b) Comparison.LESS_THAN
|
||||
else Comparison.GREATER
|
||||
})
|
||||
}
|
||||
},
|
||||
FLOAT("float") {
|
||||
override fun parse(element: JsonElement): NbtMatcher? {
|
||||
return parseGenericNumber(element,
|
||||
{ it.asFloat },
|
||||
{ (it as? NbtFloat)?.floatValue() },
|
||||
{ a, b ->
|
||||
if (a == b) Comparison.EQUAL
|
||||
else if (a < b) Comparison.LESS_THAN
|
||||
else Comparison.GREATER
|
||||
})
|
||||
}
|
||||
},
|
||||
DOUBLE("double") {
|
||||
override fun parse(element: JsonElement): NbtMatcher? {
|
||||
return parseGenericNumber(element,
|
||||
{ it.asDouble },
|
||||
{ (it as? NbtDouble)?.doubleValue() },
|
||||
{ a, b ->
|
||||
if (a == b) Comparison.EQUAL
|
||||
else if (a < b) Comparison.LESS_THAN
|
||||
else Comparison.GREATER
|
||||
})
|
||||
}
|
||||
},
|
||||
LONG("long") {
|
||||
override fun parse(element: JsonElement): NbtMatcher? {
|
||||
return parseGenericNumber(element,
|
||||
{ it.asLong },
|
||||
{ (it as? NbtLong)?.longValue() },
|
||||
{ a, b ->
|
||||
if (a == b) Comparison.EQUAL
|
||||
else if (a < b) Comparison.LESS_THAN
|
||||
else Comparison.GREATER
|
||||
})
|
||||
}
|
||||
},
|
||||
SHORT("short") {
|
||||
override fun parse(element: JsonElement): NbtMatcher? {
|
||||
return parseGenericNumber(element,
|
||||
{ it.asShort },
|
||||
{ (it as? NbtShort)?.shortValue() },
|
||||
{ a, b ->
|
||||
if (a == b) Comparison.EQUAL
|
||||
else if (a < b) Comparison.LESS_THAN
|
||||
else Comparison.GREATER
|
||||
})
|
||||
}
|
||||
},
|
||||
BYTE("byte") {
|
||||
override fun parse(element: JsonElement): NbtMatcher? {
|
||||
return parseGenericNumber(element,
|
||||
{ it.asByte },
|
||||
{ (it as? NbtByte)?.byteValue() },
|
||||
{ a, b ->
|
||||
if (a == b) Comparison.EQUAL
|
||||
else if (a < b) Comparison.LESS_THAN
|
||||
else Comparison.GREATER
|
||||
})
|
||||
}
|
||||
},
|
||||
;
|
||||
|
||||
abstract fun parse(element: JsonElement): NbtMatcher?
|
||||
}
|
||||
|
||||
enum class Comparison {
|
||||
LESS_THAN, EQUAL, GREATER
|
||||
}
|
||||
|
||||
inline fun <T : Any> parseGenericNumber(
|
||||
jsonElement: JsonElement,
|
||||
primitiveExtractor: (JsonPrimitive) -> T?,
|
||||
crossinline nbtExtractor: (NbtElement) -> T?,
|
||||
crossinline compare: (T, T) -> Comparison
|
||||
): NbtMatcher? {
|
||||
if (jsonElement is JsonPrimitive) {
|
||||
val expected = primitiveExtractor(jsonElement) ?: return null
|
||||
return NbtMatcher {
|
||||
val actual = nbtExtractor(it) ?: return@NbtMatcher false
|
||||
compare(actual, expected) == Comparison.EQUAL
|
||||
}
|
||||
}
|
||||
if (jsonElement is JsonObject) {
|
||||
val minElement = jsonElement.getAsJsonPrimitive("min")
|
||||
val min = if (minElement != null) primitiveExtractor(minElement) ?: return null else null
|
||||
val minExclusive = jsonElement.get("minExclusive")?.asBoolean ?: false
|
||||
val maxElement = jsonElement.getAsJsonPrimitive("max")
|
||||
val max = if (maxElement != null) primitiveExtractor(maxElement) ?: return null else null
|
||||
val maxExclusive = jsonElement.get("maxExclusive")?.asBoolean ?: true
|
||||
if (min == null && max == null) return null
|
||||
return NbtMatcher {
|
||||
val actual = nbtExtractor(it) ?: return@NbtMatcher false
|
||||
if (max != null) {
|
||||
val comp = compare(actual, max)
|
||||
if (comp == Comparison.GREATER) return@NbtMatcher false
|
||||
if (comp == Comparison.EQUAL && maxExclusive) return@NbtMatcher false
|
||||
}
|
||||
if (min != null) {
|
||||
val comp = compare(actual, min)
|
||||
if (comp == Comparison.LESS_THAN) return@NbtMatcher false
|
||||
if (comp == Comparison.EQUAL && minExclusive) return@NbtMatcher false
|
||||
}
|
||||
return@NbtMatcher true
|
||||
}
|
||||
}
|
||||
return null
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
class MatchNumberExact(val number: Long) : NbtMatcher {
|
||||
override fun matches(nbt: NbtElement): Boolean {
|
||||
return when (nbt) {
|
||||
is NbtByte -> nbt.byteValue().toLong() == number
|
||||
is NbtInt -> nbt.intValue().toLong() == number
|
||||
is NbtShort -> nbt.shortValue().toLong() == number
|
||||
is NbtLong -> nbt.longValue().toLong() == number
|
||||
else -> false
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
class MatchStringExact(val string: String) : NbtMatcher {
|
||||
override fun matches(nbt: NbtElement): Boolean {
|
||||
return nbt is NbtString && nbt.asString() == string
|
||||
}
|
||||
|
||||
override fun toString(): String {
|
||||
return "MatchNbtStringExactly($string)"
|
||||
}
|
||||
}
|
||||
|
||||
class MatchString(val string: StringMatcher) : NbtMatcher {
|
||||
override fun matches(nbt: NbtElement): Boolean {
|
||||
return nbt is NbtString && string.matches(nbt.asString())
|
||||
}
|
||||
|
||||
override fun toString(): String {
|
||||
return "MatchNbtString($string)"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
data class ExtraAttributesPredicate(
|
||||
val path: NbtPrism,
|
||||
val matcher: NbtMatcher,
|
||||
) : FirmamentModelPredicate {
|
||||
|
||||
object Parser : FirmamentModelPredicateParser {
|
||||
override fun parse(jsonElement: JsonElement): FirmamentModelPredicate? {
|
||||
if (jsonElement !is JsonObject) return null
|
||||
val path = jsonElement.get("path") ?: return null
|
||||
val pathSegments = if (path is JsonArray) {
|
||||
path.map { (it as JsonPrimitive).asString }
|
||||
} else if (path is JsonPrimitive && path.isString) {
|
||||
path.asString.split(".")
|
||||
} else return null
|
||||
val matcher = NbtMatcher.Parser.parse(jsonElement.get("match") ?: jsonElement)
|
||||
?: return null
|
||||
return ExtraAttributesPredicate(NbtPrism(pathSegments), matcher)
|
||||
}
|
||||
}
|
||||
|
||||
override fun test(stack: ItemStack): Boolean {
|
||||
return path.access(stack.extraAttributes)
|
||||
.any { matcher.matches(it) }
|
||||
}
|
||||
}
|
||||
|
||||
class NbtPrism(val path: List<String>) {
|
||||
override fun toString(): String {
|
||||
return "Prism($path)"
|
||||
}
|
||||
fun access(root: NbtElement): Collection<NbtElement> {
|
||||
var rootSet = mutableListOf(root)
|
||||
var switch = mutableListOf<NbtElement>()
|
||||
for (pathSegment in path) {
|
||||
if (pathSegment == ".") continue
|
||||
for (element in rootSet) {
|
||||
if (element is NbtList) {
|
||||
if (pathSegment == "*")
|
||||
switch.addAll(element)
|
||||
val index = pathSegment.toIntOrNull() ?: continue
|
||||
if (index !in element.indices) continue
|
||||
switch.add(element[index])
|
||||
}
|
||||
if (element is NbtCompound) {
|
||||
if (pathSegment == "*")
|
||||
element.keys.mapTo(switch) { element.get(it)!! }
|
||||
switch.add(element.get(pathSegment) ?: continue)
|
||||
}
|
||||
}
|
||||
val temp = switch
|
||||
switch = rootSet
|
||||
rootSet = temp
|
||||
switch.clear()
|
||||
}
|
||||
return rootSet
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
|
||||
package moe.nea.firmament.features.texturepack.predicates
|
||||
|
||||
import com.google.gson.JsonElement
|
||||
import com.google.gson.JsonPrimitive
|
||||
import moe.nea.firmament.features.texturepack.FirmamentModelPredicate
|
||||
import moe.nea.firmament.features.texturepack.FirmamentModelPredicateParser
|
||||
import kotlin.jvm.optionals.getOrNull
|
||||
import net.minecraft.item.Item
|
||||
import net.minecraft.item.ItemStack
|
||||
import net.minecraft.registry.RegistryKey
|
||||
import net.minecraft.registry.RegistryKeys
|
||||
import net.minecraft.util.Identifier
|
||||
import moe.nea.firmament.util.MC
|
||||
|
||||
class ItemPredicate(
|
||||
val item: Item
|
||||
) : FirmamentModelPredicate {
|
||||
override fun test(stack: ItemStack): Boolean {
|
||||
return stack.item == item
|
||||
}
|
||||
|
||||
object Parser : FirmamentModelPredicateParser {
|
||||
override fun parse(jsonElement: JsonElement): ItemPredicate? {
|
||||
if (jsonElement is JsonPrimitive && jsonElement.isString) {
|
||||
val itemKey = RegistryKey.of(RegistryKeys.ITEM,
|
||||
Identifier.tryParse(jsonElement.asString)
|
||||
?: return null)
|
||||
return ItemPredicate(MC.defaultItems.getOptional(itemKey).getOrNull()?.value() ?: return null)
|
||||
}
|
||||
return null
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
|
||||
package moe.nea.firmament.features.texturepack.predicates
|
||||
|
||||
import com.google.gson.JsonElement
|
||||
import moe.nea.firmament.features.texturepack.FirmamentModelPredicate
|
||||
import moe.nea.firmament.features.texturepack.FirmamentModelPredicateParser
|
||||
import moe.nea.firmament.features.texturepack.StringMatcher
|
||||
import net.minecraft.item.ItemStack
|
||||
import moe.nea.firmament.util.mc.loreAccordingToNbt
|
||||
|
||||
class LorePredicate(val matcher: StringMatcher) : FirmamentModelPredicate {
|
||||
object Parser : FirmamentModelPredicateParser {
|
||||
override fun parse(jsonElement: JsonElement): FirmamentModelPredicate {
|
||||
return LorePredicate(StringMatcher.parse(jsonElement))
|
||||
}
|
||||
}
|
||||
|
||||
override fun test(stack: ItemStack): Boolean {
|
||||
val lore = stack.loreAccordingToNbt
|
||||
return lore.any { matcher.matches(it) }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
|
||||
package moe.nea.firmament.features.texturepack.predicates
|
||||
|
||||
import com.google.gson.JsonElement
|
||||
import com.google.gson.JsonObject
|
||||
import moe.nea.firmament.features.texturepack.CustomModelOverrideParser
|
||||
import moe.nea.firmament.features.texturepack.FirmamentModelPredicate
|
||||
import moe.nea.firmament.features.texturepack.FirmamentModelPredicateParser
|
||||
import net.minecraft.item.ItemStack
|
||||
|
||||
class NotPredicate(val children: Array<FirmamentModelPredicate>) : FirmamentModelPredicate {
|
||||
override fun test(stack: ItemStack): Boolean {
|
||||
return children.none { it.test(stack) }
|
||||
}
|
||||
|
||||
object Parser : FirmamentModelPredicateParser {
|
||||
override fun parse(jsonElement: JsonElement): FirmamentModelPredicate {
|
||||
return NotPredicate(CustomModelOverrideParser.parsePredicates(jsonElement as JsonObject).toTypedArray())
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,124 @@
|
||||
package moe.nea.firmament.features.texturepack.predicates
|
||||
|
||||
import com.google.gson.JsonElement
|
||||
import com.google.gson.JsonPrimitive
|
||||
import moe.nea.firmament.util.useMatch
|
||||
|
||||
abstract class NumberMatcher {
|
||||
abstract fun test(number: Number): Boolean
|
||||
|
||||
|
||||
companion object {
|
||||
fun parse(jsonElement: JsonElement): NumberMatcher? {
|
||||
if (jsonElement is JsonPrimitive) {
|
||||
if (jsonElement.isString) {
|
||||
val string = jsonElement.asString
|
||||
return parseRange(string) ?: parseOperator(string)
|
||||
}
|
||||
if (jsonElement.isNumber) {
|
||||
val number = jsonElement.asNumber
|
||||
val hasDecimals = (number.toString().contains("."))
|
||||
return MatchNumberExact(if (hasDecimals) number.toLong() else number.toDouble())
|
||||
}
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
private val intervalSpec =
|
||||
"(?<beginningOpen>[\\[\\(])(?<beginning>[0-9.]+)?,(?<ending>[0-9.]+)?(?<endingOpen>[\\]\\)])"
|
||||
.toPattern()
|
||||
|
||||
fun parseRange(string: String): RangeMatcher? {
|
||||
intervalSpec.useMatch<Nothing>(string) {
|
||||
// Open in the set-theory sense, meaning does not include its end.
|
||||
val beginningOpen = group("beginningOpen") == "("
|
||||
val endingOpen = group("endingOpen") == ")"
|
||||
val beginning = group("beginning")?.toDouble()
|
||||
val ending = group("ending")?.toDouble()
|
||||
return RangeMatcher(beginning, !beginningOpen, ending, !endingOpen)
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
enum class Operator(val operator: String) {
|
||||
LESS("<") {
|
||||
override fun matches(comparisonResult: Int): Boolean {
|
||||
return comparisonResult < 0
|
||||
}
|
||||
},
|
||||
LESS_EQUALS("<=") {
|
||||
override fun matches(comparisonResult: Int): Boolean {
|
||||
return comparisonResult <= 0
|
||||
}
|
||||
},
|
||||
GREATER(">") {
|
||||
override fun matches(comparisonResult: Int): Boolean {
|
||||
return comparisonResult > 0
|
||||
}
|
||||
},
|
||||
GREATER_EQUALS(">=") {
|
||||
override fun matches(comparisonResult: Int): Boolean {
|
||||
return comparisonResult >= 0
|
||||
}
|
||||
},
|
||||
;
|
||||
|
||||
abstract fun matches(comparisonResult: Int): Boolean
|
||||
}
|
||||
|
||||
private val operatorPattern =
|
||||
"(?<operator>${Operator.entries.joinToString("|") { it.operator }})(?<value>[0-9.]+)".toPattern()
|
||||
|
||||
fun parseOperator(string: String): OperatorMatcher? {
|
||||
return operatorPattern.useMatch(string) {
|
||||
val operatorName = group("operator")
|
||||
val operator = Operator.entries.find { it.operator == operatorName }!!
|
||||
val value = group("value").toDouble()
|
||||
OperatorMatcher(operator, value)
|
||||
}
|
||||
}
|
||||
|
||||
data class OperatorMatcher(val operator: Operator, val value: Double) : NumberMatcher() {
|
||||
override fun test(number: Number): Boolean {
|
||||
return operator.matches(number.toDouble().compareTo(value))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
data class MatchNumberExact(val number: Number) : NumberMatcher() {
|
||||
override fun test(number: Number): Boolean {
|
||||
return when (this.number) {
|
||||
is Double -> number.toDouble() == this.number.toDouble()
|
||||
else -> number.toLong() == this.number.toLong()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
data class RangeMatcher(
|
||||
val beginning: Double?,
|
||||
val beginningInclusive: Boolean,
|
||||
val ending: Double?,
|
||||
val endingInclusive: Boolean,
|
||||
) : NumberMatcher() {
|
||||
override fun test(number: Number): Boolean {
|
||||
val value = number.toDouble()
|
||||
if (beginning != null) {
|
||||
if (beginningInclusive) {
|
||||
if (value < beginning) return false
|
||||
} else {
|
||||
if (value <= beginning) return false
|
||||
}
|
||||
}
|
||||
if (ending != null) {
|
||||
if (endingInclusive) {
|
||||
if (value > ending) return false
|
||||
} else {
|
||||
if (value >= ending) return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
|
||||
package moe.nea.firmament.features.texturepack.predicates
|
||||
|
||||
import com.google.gson.JsonArray
|
||||
import com.google.gson.JsonElement
|
||||
import com.google.gson.JsonObject
|
||||
import moe.nea.firmament.features.texturepack.CustomModelOverrideParser
|
||||
import moe.nea.firmament.features.texturepack.FirmamentModelPredicate
|
||||
import moe.nea.firmament.features.texturepack.FirmamentModelPredicateParser
|
||||
import net.minecraft.item.ItemStack
|
||||
|
||||
class OrPredicate(val children: Array<FirmamentModelPredicate>) : FirmamentModelPredicate {
|
||||
override fun test(stack: ItemStack): Boolean {
|
||||
return children.any { it.test(stack) }
|
||||
}
|
||||
|
||||
object Parser : FirmamentModelPredicateParser {
|
||||
override fun parse(jsonElement: JsonElement): FirmamentModelPredicate {
|
||||
val children =
|
||||
(jsonElement as JsonArray)
|
||||
.flatMap {
|
||||
CustomModelOverrideParser.parsePredicates(it as JsonObject)
|
||||
}
|
||||
.toTypedArray()
|
||||
return OrPredicate(children)
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,66 @@
|
||||
|
||||
package moe.nea.firmament.features.texturepack.predicates
|
||||
|
||||
import com.google.gson.JsonElement
|
||||
import com.google.gson.JsonObject
|
||||
import moe.nea.firmament.features.texturepack.FirmamentModelPredicate
|
||||
import moe.nea.firmament.features.texturepack.FirmamentModelPredicateParser
|
||||
import moe.nea.firmament.features.texturepack.RarityMatcher
|
||||
import moe.nea.firmament.features.texturepack.StringMatcher
|
||||
import net.minecraft.item.ItemStack
|
||||
import moe.nea.firmament.repo.ExpLadders
|
||||
import moe.nea.firmament.util.petData
|
||||
|
||||
data class PetPredicate(
|
||||
val petId: StringMatcher?,
|
||||
val tier: RarityMatcher?,
|
||||
val exp: NumberMatcher?,
|
||||
val candyUsed: NumberMatcher?,
|
||||
val level: NumberMatcher?,
|
||||
) : FirmamentModelPredicate {
|
||||
|
||||
override fun test(stack: ItemStack): Boolean {
|
||||
val petData = stack.petData ?: return false
|
||||
if (petId != null) {
|
||||
if (!petId.matches(petData.type)) return false
|
||||
}
|
||||
if (exp != null) {
|
||||
if (!exp.test(petData.exp)) return false
|
||||
}
|
||||
if (candyUsed != null) {
|
||||
if (!candyUsed.test(petData.candyUsed)) return false
|
||||
}
|
||||
if (tier != null) {
|
||||
if (!tier.match(petData.tier)) return false
|
||||
}
|
||||
val levelData by lazy(LazyThreadSafetyMode.NONE) {
|
||||
ExpLadders.getExpLadder(petData.type, petData.tier)
|
||||
.getPetLevel(petData.exp)
|
||||
}
|
||||
if (level != null) {
|
||||
if (!level.test(levelData.currentLevel)) return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
object Parser : FirmamentModelPredicateParser {
|
||||
override fun parse(jsonElement: JsonElement): FirmamentModelPredicate? {
|
||||
if (jsonElement.isJsonPrimitive) {
|
||||
return PetPredicate(StringMatcher.Equals(jsonElement.asString, false), null, null, null, null)
|
||||
}
|
||||
if (jsonElement !is JsonObject) return null
|
||||
val idMatcher = jsonElement["id"]?.let(StringMatcher::parse)
|
||||
val expMatcher = jsonElement["exp"]?.let(NumberMatcher::parse)
|
||||
val levelMatcher = jsonElement["level"]?.let(NumberMatcher::parse)
|
||||
val candyMatcher = jsonElement["candyUsed"]?.let(NumberMatcher::parse)
|
||||
val tierMatcher = jsonElement["tier"]?.let(RarityMatcher::parse)
|
||||
return PetPredicate(
|
||||
idMatcher,
|
||||
tierMatcher,
|
||||
expMatcher,
|
||||
candyMatcher,
|
||||
levelMatcher,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
|
||||
package moe.nea.firmament.mixins.custommodels;
|
||||
|
||||
import net.minecraft.client.item.ItemModelManager;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
|
||||
@Mixin(ItemModelManager.class)
|
||||
public class ApplyHeadModelInItemRenderer {
|
||||
// TODO: replace head_model with a condition model (if possible, automatically)
|
||||
// TODO: ItemAsset.CODEC should upgrade partials
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
|
||||
|
||||
package moe.nea.firmament.mixins.custommodels;
|
||||
|
||||
import moe.nea.firmament.features.texturepack.CustomSkyBlockTextures;
|
||||
import net.minecraft.block.SkullBlock;
|
||||
import net.minecraft.client.render.RenderLayer;
|
||||
import net.minecraft.client.render.block.entity.SkullBlockEntityRenderer;
|
||||
import net.minecraft.component.type.ProfileComponent;
|
||||
import net.minecraft.util.Identifier;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.injection.At;
|
||||
import org.spongepowered.asm.mixin.injection.Inject;
|
||||
import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
|
||||
|
||||
@Mixin(SkullBlockEntityRenderer.class)
|
||||
public class CustomSkullTexturePatch {
|
||||
@Inject(
|
||||
method = "getRenderLayer(Lnet/minecraft/block/SkullBlock$SkullType;Lnet/minecraft/component/type/ProfileComponent;Lnet/minecraft/util/Identifier;)Lnet/minecraft/client/render/RenderLayer;",
|
||||
at = @At("HEAD"),
|
||||
cancellable = true
|
||||
)
|
||||
private static void onGetRenderLayer(SkullBlock.SkullType type, ProfileComponent profile, Identifier texture, CallbackInfoReturnable<RenderLayer> cir) {
|
||||
CustomSkyBlockTextures.INSTANCE.modifySkullTexture(type, profile, cir);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
|
||||
package moe.nea.firmament.mixins.custommodels;
|
||||
|
||||
import com.llamalad7.mixinextras.injector.ModifyExpressionValue;
|
||||
import com.llamalad7.mixinextras.injector.wrapoperation.Operation;
|
||||
import com.llamalad7.mixinextras.injector.wrapoperation.WrapOperation;
|
||||
import com.llamalad7.mixinextras.sugar.Local;
|
||||
import moe.nea.firmament.features.texturepack.CustomGlobalArmorOverrides;
|
||||
import net.minecraft.client.render.entity.feature.ArmorFeatureRenderer;
|
||||
import net.minecraft.component.ComponentType;
|
||||
import net.minecraft.component.type.EquippableComponent;
|
||||
import net.minecraft.entity.EquipmentSlot;
|
||||
import net.minecraft.item.ItemStack;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.injection.At;
|
||||
|
||||
@Mixin(ArmorFeatureRenderer.class)
|
||||
public class PatchArmorTexture {
|
||||
@ModifyExpressionValue(
|
||||
method = "renderArmor",
|
||||
at = @At(
|
||||
value = "INVOKE",
|
||||
target = "Lnet/minecraft/item/ItemStack;get(Lnet/minecraft/component/ComponentType;)Ljava/lang/Object;"))
|
||||
private Object overrideLayers(
|
||||
Object original, @Local(argsOnly = true) ItemStack itemStack, @Local(argsOnly = true) EquipmentSlot slot
|
||||
) {
|
||||
var overrides = CustomGlobalArmorOverrides.overrideArmor(itemStack, slot);
|
||||
return overrides.orElse((EquippableComponent) original);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
package moe.nea.firmament.mixins.custommodels;
|
||||
|
||||
import com.llamalad7.mixinextras.injector.wrapoperation.Operation;
|
||||
import com.llamalad7.mixinextras.injector.wrapoperation.WrapOperation;
|
||||
import moe.nea.firmament.features.texturepack.CustomGlobalArmorOverrides;
|
||||
import net.minecraft.client.render.entity.equipment.EquipmentModel;
|
||||
import net.minecraft.client.render.entity.equipment.EquipmentModelLoader;
|
||||
import net.minecraft.client.render.entity.equipment.EquipmentRenderer;
|
||||
import net.minecraft.item.equipment.EquipmentAsset;
|
||||
import net.minecraft.registry.RegistryKey;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.injection.At;
|
||||
|
||||
// TODO: auto import legacy models, maybe!!! in a later patch tho
|
||||
@Mixin(EquipmentRenderer.class)
|
||||
public class PatchLegacyArmorLayerSupport {
|
||||
@WrapOperation(method = "render(Lnet/minecraft/client/render/entity/equipment/EquipmentModel$LayerType;Lnet/minecraft/registry/RegistryKey;Lnet/minecraft/client/model/Model;Lnet/minecraft/item/ItemStack;Lnet/minecraft/client/util/math/MatrixStack;Lnet/minecraft/client/render/VertexConsumerProvider;ILnet/minecraft/util/Identifier;)V", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/render/entity/equipment/EquipmentModelLoader;get(Lnet/minecraft/registry/RegistryKey;)Lnet/minecraft/client/render/entity/equipment/EquipmentModel;"))
|
||||
private EquipmentModel patchModelLayers(EquipmentModelLoader instance, RegistryKey<EquipmentAsset> assetKey, Operation<EquipmentModel> original) {
|
||||
var modelOverride = CustomGlobalArmorOverrides.overrideArmorLayer(assetKey.getValue());
|
||||
if (modelOverride != null) return modelOverride;
|
||||
return original.call(instance, assetKey);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
package moe.nea.firmament.mixins.custommodels;
|
||||
|
||||
import com.llamalad7.mixinextras.sugar.Local;
|
||||
import moe.nea.firmament.events.BakeExtraModelsEvent;
|
||||
import net.minecraft.client.item.ItemAssetsLoader;
|
||||
import net.minecraft.client.render.model.BakedModelManager;
|
||||
import net.minecraft.client.render.model.BlockStatesLoader;
|
||||
import net.minecraft.client.render.model.ReferencedModelsCollector;
|
||||
import net.minecraft.client.render.model.UnbakedModel;
|
||||
import net.minecraft.client.util.ModelIdentifier;
|
||||
import net.minecraft.util.Identifier;
|
||||
import org.spongepowered.asm.mixin.Final;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.Shadow;
|
||||
import org.spongepowered.asm.mixin.injection.At;
|
||||
import org.spongepowered.asm.mixin.injection.Inject;
|
||||
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
|
||||
import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
@Mixin(BakedModelManager.class)
|
||||
public abstract class ReferenceCustomModelsPatch {
|
||||
@Inject(method = "collect", at = @At("RETURN"))
|
||||
private static void addFirmamentReferencedModels(
|
||||
UnbakedModel missingModel, Map<Identifier, UnbakedModel> models, BlockStatesLoader.BlockStateDefinition blockStates, ItemAssetsLoader.Result itemAssets, CallbackInfoReturnable<ReferencedModelsCollector> cir,
|
||||
@Local ReferencedModelsCollector collector) {
|
||||
// TODO: Insert fake models based on firmskyblock models for a smoother transition
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
package moe.nea.firmament.mixins.custommodels;
|
||||
|
||||
import com.llamalad7.mixinextras.injector.wrapoperation.Operation;
|
||||
import com.llamalad7.mixinextras.injector.wrapoperation.WrapOperation;
|
||||
import com.llamalad7.mixinextras.sugar.Local;
|
||||
import moe.nea.firmament.features.texturepack.CustomBlockTextures;
|
||||
import net.minecraft.block.BlockState;
|
||||
import net.minecraft.client.render.WorldRenderer;
|
||||
import net.minecraft.sound.BlockSoundGroup;
|
||||
import net.minecraft.sound.SoundEvent;
|
||||
import net.minecraft.util.math.BlockPos;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.injection.At;
|
||||
|
||||
@Mixin(WorldRenderer.class)
|
||||
public class ReplaceBlockBreakSoundPatch {
|
||||
// Sadly hypixel does not send a world event here and instead plays the sound on the server directly
|
||||
// @WrapOperation(method = "processWorldEvent", at = @At(value = "INVOKE", target = "Lnet/minecraft/sound/BlockSoundGroup;getBreakSound()Lnet/minecraft/sound/SoundEvent;"))
|
||||
// private SoundEvent replaceBreakSoundEvent(BlockSoundGroup instance, Operation<SoundEvent> original,
|
||||
// @Local(argsOnly = true) BlockPos pos, @Local BlockState blockState) {
|
||||
// var replacement = CustomBlockTextures.getReplacement(blockState, pos);
|
||||
// if (replacement != null && replacement.getSound() != null) {
|
||||
// return SoundEvent.of(replacement.getSound());
|
||||
// }
|
||||
// return original.call(instance);
|
||||
// }
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
package moe.nea.firmament.mixins.custommodels;
|
||||
|
||||
import com.llamalad7.mixinextras.injector.wrapoperation.Operation;
|
||||
import com.llamalad7.mixinextras.injector.wrapoperation.WrapOperation;
|
||||
import com.llamalad7.mixinextras.sugar.Local;
|
||||
import moe.nea.firmament.features.texturepack.CustomBlockTextures;
|
||||
import net.minecraft.block.BlockState;
|
||||
import net.minecraft.client.network.ClientPlayerInteractionManager;
|
||||
import net.minecraft.client.sound.PositionedSoundInstance;
|
||||
import net.minecraft.sound.SoundCategory;
|
||||
import net.minecraft.sound.SoundEvent;
|
||||
import net.minecraft.util.math.BlockPos;
|
||||
import net.minecraft.util.math.random.Random;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.injection.At;
|
||||
|
||||
@Mixin(ClientPlayerInteractionManager.class)
|
||||
public class ReplaceBlockHitSoundPatch {
|
||||
@WrapOperation(method = "updateBlockBreakingProgress", at = @At(value = "NEW", target = "(Lnet/minecraft/sound/SoundEvent;Lnet/minecraft/sound/SoundCategory;FFLnet/minecraft/util/math/random/Random;Lnet/minecraft/util/math/BlockPos;)Lnet/minecraft/client/sound/PositionedSoundInstance;"))
|
||||
private PositionedSoundInstance replaceSound(
|
||||
SoundEvent sound, SoundCategory category, float volume, float pitch,
|
||||
Random random, BlockPos pos, Operation<PositionedSoundInstance> original,
|
||||
@Local BlockState blockState) {
|
||||
var replacement = CustomBlockTextures.getReplacement(blockState, pos);
|
||||
if (replacement != null && replacement.getSound() != null) {
|
||||
sound = SoundEvent.of(replacement.getSound());
|
||||
}
|
||||
return original.call(sound, category, volume, pitch, random, pos);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
package moe.nea.firmament.mixins.custommodels;
|
||||
|
||||
import com.llamalad7.mixinextras.injector.wrapoperation.Operation;
|
||||
import com.llamalad7.mixinextras.injector.wrapoperation.WrapOperation;
|
||||
import com.llamalad7.mixinextras.sugar.Local;
|
||||
import moe.nea.firmament.features.texturepack.CustomBlockTextures;
|
||||
import net.minecraft.block.BlockState;
|
||||
import net.minecraft.client.render.block.BlockModels;
|
||||
import net.minecraft.client.render.block.BlockRenderManager;
|
||||
import net.minecraft.client.render.model.BakedModel;
|
||||
import net.minecraft.util.math.BlockPos;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.injection.At;
|
||||
|
||||
@Mixin(BlockRenderManager.class)
|
||||
public class ReplaceBlockRenderManagerBlockModel {
|
||||
@WrapOperation(method = "renderBlock", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/render/block/BlockRenderManager;getModel(Lnet/minecraft/block/BlockState;)Lnet/minecraft/client/render/model/BakedModel;"))
|
||||
private BakedModel replaceModelInRenderBlock(
|
||||
BlockRenderManager instance, BlockState state, Operation<BakedModel> original, @Local(argsOnly = true) BlockPos pos) {
|
||||
var replacement = CustomBlockTextures.getReplacementModel(state, pos);
|
||||
if (replacement != null) return replacement;
|
||||
CustomBlockTextures.enterFallbackCall();
|
||||
var fallback = original.call(instance, state);
|
||||
CustomBlockTextures.exitFallbackCall();
|
||||
return fallback;
|
||||
}
|
||||
|
||||
@WrapOperation(method = "renderDamage", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/render/block/BlockModels;getModel(Lnet/minecraft/block/BlockState;)Lnet/minecraft/client/render/model/BakedModel;"))
|
||||
private BakedModel replaceModelInRenderDamage(
|
||||
BlockModels instance, BlockState state, Operation<BakedModel> original, @Local(argsOnly = true) BlockPos pos) {
|
||||
var replacement = CustomBlockTextures.getReplacementModel(state, pos);
|
||||
if (replacement != null) return replacement;
|
||||
CustomBlockTextures.enterFallbackCall();
|
||||
var fallback = original.call(instance, state);
|
||||
CustomBlockTextures.exitFallbackCall();
|
||||
return fallback;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
package moe.nea.firmament.mixins.custommodels;
|
||||
|
||||
import moe.nea.firmament.features.texturepack.CustomBlockTextures;
|
||||
import net.minecraft.block.BlockState;
|
||||
import net.minecraft.client.render.block.BlockModels;
|
||||
import net.minecraft.client.render.model.BakedModel;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.injection.At;
|
||||
import org.spongepowered.asm.mixin.injection.Inject;
|
||||
import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
|
||||
|
||||
@Mixin(BlockModels.class)
|
||||
public class ReplaceFallbackBlockModel {
|
||||
// TODO: add check to BlockDustParticle
|
||||
@Inject(method = "getModel", at = @At("HEAD"), cancellable = true)
|
||||
private void getModel(BlockState state, CallbackInfoReturnable<BakedModel> cir) {
|
||||
var replacement = CustomBlockTextures.getReplacementModel(state, null);
|
||||
if (replacement != null)
|
||||
cir.setReturnValue(replacement);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
package moe.nea.firmament.mixins.custommodels;
|
||||
|
||||
|
||||
import com.llamalad7.mixinextras.injector.wrapoperation.Operation;
|
||||
import com.llamalad7.mixinextras.injector.wrapoperation.WrapOperation;
|
||||
import moe.nea.firmament.events.CustomItemModelEvent;
|
||||
import net.minecraft.client.item.ItemModelManager;
|
||||
import net.minecraft.component.ComponentType;
|
||||
import net.minecraft.item.ItemStack;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.injection.At;
|
||||
|
||||
@Mixin(ItemModelManager.class)
|
||||
public class ReplaceItemModelPatch {
|
||||
@WrapOperation(
|
||||
method = "update(Lnet/minecraft/client/render/item/ItemRenderState;Lnet/minecraft/item/ItemStack;Lnet/minecraft/item/ModelTransformationMode;Lnet/minecraft/world/World;Lnet/minecraft/entity/LivingEntity;I)V",
|
||||
at = @At(value = "INVOKE", target = "Lnet/minecraft/item/ItemStack;get(Lnet/minecraft/component/ComponentType;)Ljava/lang/Object;"))
|
||||
private Object replaceItemModelByIdentifier(ItemStack instance, ComponentType componentType, Operation<Object> original) {
|
||||
var override = CustomItemModelEvent.getModelIdentifier(instance);
|
||||
if (override != null)
|
||||
return override;
|
||||
return original.call(instance, componentType);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
package moe.nea.firmament.mixins.custommodels;
|
||||
|
||||
|
||||
import com.llamalad7.mixinextras.injector.wrapoperation.Operation;
|
||||
import com.llamalad7.mixinextras.injector.wrapoperation.WrapOperation;
|
||||
import moe.nea.firmament.features.texturepack.CustomTextColors;
|
||||
import net.minecraft.client.font.TextRenderer;
|
||||
import net.minecraft.client.gui.DrawContext;
|
||||
import net.minecraft.client.gui.screen.ingame.AnvilScreen;
|
||||
import net.minecraft.client.gui.screen.ingame.BeaconScreen;
|
||||
import net.minecraft.client.gui.screen.ingame.CreativeInventoryScreen;
|
||||
import net.minecraft.client.gui.screen.ingame.HandledScreen;
|
||||
import net.minecraft.client.gui.screen.ingame.InventoryScreen;
|
||||
import net.minecraft.client.gui.screen.ingame.MerchantScreen;
|
||||
import net.minecraft.text.Text;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.injection.At;
|
||||
|
||||
@Mixin({HandledScreen.class, InventoryScreen.class, CreativeInventoryScreen.class, MerchantScreen.class,
|
||||
AnvilScreen.class, BeaconScreen.class})
|
||||
public class ReplaceTextColorInHandledScreen {
|
||||
|
||||
// To my future self: double check those mixins, but don't be too concerned about errors. Some of the wrapopertions
|
||||
// only apply in some of the specified subclasses.
|
||||
|
||||
@WrapOperation(
|
||||
method = "drawForeground",
|
||||
at = @At(
|
||||
value = "INVOKE",
|
||||
target = "Lnet/minecraft/client/gui/DrawContext;drawText(Lnet/minecraft/client/font/TextRenderer;Lnet/minecraft/text/Text;IIIZ)I"),
|
||||
expect = 0,
|
||||
require = 0)
|
||||
private int replaceTextColorWithVariableShadow(DrawContext instance, TextRenderer textRenderer, Text text, int x, int y, int color, boolean shadow, Operation<Integer> original) {
|
||||
return original.call(instance, textRenderer, text, x, y, CustomTextColors.INSTANCE.mapTextColor(text, color), shadow);
|
||||
}
|
||||
|
||||
@WrapOperation(
|
||||
method = "drawForeground",
|
||||
at = @At(
|
||||
value = "INVOKE",
|
||||
target = "Lnet/minecraft/client/gui/DrawContext;drawTextWithShadow(Lnet/minecraft/client/font/TextRenderer;Lnet/minecraft/text/Text;III)I"),
|
||||
expect = 0,
|
||||
require = 0)
|
||||
private int replaceTextColorWithShadow(DrawContext instance, TextRenderer textRenderer, Text text, int x, int y, int color, Operation<Integer> original) {
|
||||
return original.call(instance, textRenderer, text, x, y, CustomTextColors.INSTANCE.mapTextColor(text, color));
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user