aboutsummaryrefslogtreecommitdiff
path: root/src/texturePacks
diff options
context:
space:
mode:
Diffstat (limited to 'src/texturePacks')
-rw-r--r--src/texturePacks/java/moe/nea/firmament/features/texturepack/Compat.kt11
-rw-r--r--src/texturePacks/java/moe/nea/firmament/features/texturepack/CustomBlockTextures.kt684
-rw-r--r--src/texturePacks/java/moe/nea/firmament/features/texturepack/CustomBlockTexturesDebugger.kt132
-rw-r--r--src/texturePacks/java/moe/nea/firmament/features/texturepack/CustomGlobalArmorOverrides.kt107
-rw-r--r--src/texturePacks/java/moe/nea/firmament/features/texturepack/CustomGlobalTextures.kt54
-rw-r--r--src/texturePacks/java/moe/nea/firmament/features/texturepack/CustomModelOverrideParser.kt29
-rw-r--r--src/texturePacks/java/moe/nea/firmament/features/texturepack/CustomScreenLayouts.kt257
-rw-r--r--src/texturePacks/java/moe/nea/firmament/features/texturepack/CustomSkyBlockTextures.kt50
-rw-r--r--src/texturePacks/java/moe/nea/firmament/features/texturepack/CustomTextColors.kt54
-rw-r--r--src/texturePacks/java/moe/nea/firmament/features/texturepack/CustomTextReplacements.kt56
-rw-r--r--src/texturePacks/java/moe/nea/firmament/features/texturepack/FirmamentModelPredicate.kt9
-rw-r--r--src/texturePacks/java/moe/nea/firmament/features/texturepack/FirmamentRootPredicateSerializer.kt1
-rw-r--r--src/texturePacks/java/moe/nea/firmament/features/texturepack/HeadModelChooser.kt92
-rw-r--r--src/texturePacks/java/moe/nea/firmament/features/texturepack/PredicateModel.kt76
-rw-r--r--src/texturePacks/java/moe/nea/firmament/features/texturepack/StringMatcher.kt269
-rw-r--r--src/texturePacks/java/moe/nea/firmament/features/texturepack/TreeishTextReplacer.kt79
-rw-r--r--src/texturePacks/java/moe/nea/firmament/features/texturepack/predicates/AlwaysPredicate.kt2
-rw-r--r--src/texturePacks/java/moe/nea/firmament/features/texturepack/predicates/AndPredicate.kt9
-rw-r--r--src/texturePacks/java/moe/nea/firmament/features/texturepack/predicates/CastPredicate.kt11
-rw-r--r--src/texturePacks/java/moe/nea/firmament/features/texturepack/predicates/DisplayNamePredicate.kt2
-rw-r--r--src/texturePacks/java/moe/nea/firmament/features/texturepack/predicates/ExtraAttributesPredicate.kt464
-rw-r--r--src/texturePacks/java/moe/nea/firmament/features/texturepack/predicates/GenericComponentPredicate.kt59
-rw-r--r--src/texturePacks/java/moe/nea/firmament/features/texturepack/predicates/ItemPredicate.kt18
-rw-r--r--src/texturePacks/java/moe/nea/firmament/features/texturepack/predicates/LorePredicate.kt2
-rw-r--r--src/texturePacks/java/moe/nea/firmament/features/texturepack/predicates/NotPredicate.kt2
-rw-r--r--src/texturePacks/java/moe/nea/firmament/features/texturepack/predicates/OrPredicate.kt2
-rw-r--r--src/texturePacks/java/moe/nea/firmament/features/texturepack/predicates/PetPredicate.kt2
-rw-r--r--src/texturePacks/java/moe/nea/firmament/features/texturepack/predicates/PullingPredicate.kt26
-rw-r--r--src/texturePacks/java/moe/nea/firmament/features/texturepack/predicates/SkullPredicate.kt63
-rw-r--r--src/texturePacks/java/moe/nea/firmament/mixins/custommodels/ApplyHeadModelInItemRenderer.java12
-rw-r--r--src/texturePacks/java/moe/nea/firmament/mixins/custommodels/BuildExtraBlockStateModels.java24
-rw-r--r--src/texturePacks/java/moe/nea/firmament/mixins/custommodels/CustomSkullTexturePatch.java19
-rw-r--r--src/texturePacks/java/moe/nea/firmament/mixins/custommodels/InsertExtraBlockModelDependencies.java28
-rw-r--r--src/texturePacks/java/moe/nea/firmament/mixins/custommodels/ItemRenderStateExtraInfo.java28
-rw-r--r--src/texturePacks/java/moe/nea/firmament/mixins/custommodels/LoadExtraBlockStates.java34
-rw-r--r--src/texturePacks/java/moe/nea/firmament/mixins/custommodels/PatchArmorTexture.java20
-rw-r--r--src/texturePacks/java/moe/nea/firmament/mixins/custommodels/PatchLegacyArmorLayerSupport.java25
-rw-r--r--src/texturePacks/java/moe/nea/firmament/mixins/custommodels/PatchLegacyTexturePathsIntoArmorLayers.java37
-rw-r--r--src/texturePacks/java/moe/nea/firmament/mixins/custommodels/ReplaceBlockBreakSoundPatch.java12
-rw-r--r--src/texturePacks/java/moe/nea/firmament/mixins/custommodels/ReplaceBlockHitSoundPatch.java29
-rw-r--r--src/texturePacks/java/moe/nea/firmament/mixins/custommodels/ReplaceBlockRenderManagerBlockModel.java53
-rw-r--r--src/texturePacks/java/moe/nea/firmament/mixins/custommodels/ReplaceFallbackBlockModel.java12
-rw-r--r--src/texturePacks/java/moe/nea/firmament/mixins/custommodels/ReplaceHeadModel.java51
-rw-r--r--src/texturePacks/java/moe/nea/firmament/mixins/custommodels/ReplaceItemModelPatch.java48
-rw-r--r--src/texturePacks/java/moe/nea/firmament/mixins/custommodels/ReplaceTextColorInHandledScreen.java48
-rw-r--r--src/texturePacks/java/moe/nea/firmament/mixins/custommodels/ReplaceTextsInDrawContext.java55
-rw-r--r--src/texturePacks/java/moe/nea/firmament/mixins/custommodels/SupplyFakeModelPatch.java75
-rw-r--r--src/texturePacks/java/moe/nea/firmament/mixins/custommodels/screenlayouts/ExpandScreenBoundaries.java21
-rw-r--r--src/texturePacks/java/moe/nea/firmament/mixins/custommodels/screenlayouts/MoveSignElements.java70
-rw-r--r--src/texturePacks/java/moe/nea/firmament/mixins/custommodels/screenlayouts/ReplaceAnvilScreen.java55
-rw-r--r--src/texturePacks/java/moe/nea/firmament/mixins/custommodels/screenlayouts/ReplaceForgingScreen.java9
-rw-r--r--src/texturePacks/java/moe/nea/firmament/mixins/custommodels/screenlayouts/ReplaceFurnaceBackgrounds.java32
-rw-r--r--src/texturePacks/java/moe/nea/firmament/mixins/custommodels/screenlayouts/ReplaceGenericBackgrounds.java28
-rw-r--r--src/texturePacks/java/moe/nea/firmament/mixins/custommodels/screenlayouts/ReplacePlayerBackgrounds.java51
-rw-r--r--src/texturePacks/java/moe/nea/firmament/mixins/custommodels/screenlayouts/ReplaceTextColorInHandledScreen.java60
55 files changed, 2525 insertions, 1033 deletions
diff --git a/src/texturePacks/java/moe/nea/firmament/features/texturepack/Compat.kt b/src/texturePacks/java/moe/nea/firmament/features/texturepack/Compat.kt
new file mode 100644
index 0000000..d95712b
--- /dev/null
+++ b/src/texturePacks/java/moe/nea/firmament/features/texturepack/Compat.kt
@@ -0,0 +1,11 @@
+package moe.nea.firmament.features.texturepack
+
+import moe.nea.firmament.util.compatloader.CompatMeta
+import moe.nea.firmament.util.compatloader.ICompatMeta
+
+@CompatMeta
+object Compat : ICompatMeta {
+ override fun shouldLoad(): Boolean {
+ return true
+ }
+}
diff --git a/src/texturePacks/java/moe/nea/firmament/features/texturepack/CustomBlockTextures.kt b/src/texturePacks/java/moe/nea/firmament/features/texturepack/CustomBlockTextures.kt
index dc3b109..bc0f36a 100644
--- a/src/texturePacks/java/moe/nea/firmament/features/texturepack/CustomBlockTextures.kt
+++ b/src/texturePacks/java/moe/nea/firmament/features/texturepack/CustomBlockTextures.kt
@@ -2,7 +2,12 @@
package moe.nea.firmament.features.texturepack
+import com.google.gson.JsonParseException
+import com.google.gson.JsonParser
+import com.mojang.serialization.JsonOps
import java.util.concurrent.CompletableFuture
+import java.util.concurrent.Executor
+import java.util.function.Function
import net.fabricmc.loader.api.FabricLoader
import kotlinx.serialization.ExperimentalSerializationApi
import kotlinx.serialization.KSerializer
@@ -17,23 +22,37 @@ 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 net.minecraft.world.level.block.Block
+import net.minecraft.world.level.block.state.BlockState
+import net.minecraft.world.level.block.Blocks
+import net.minecraft.client.resources.model.ModelBaker
+import net.minecraft.client.renderer.block.model.BlockStateModel
+import net.minecraft.client.resources.model.BlockStateModelLoader
+import net.minecraft.client.resources.model.ModelDiscovery
+import net.minecraft.client.renderer.block.model.SingleVariant
+import net.minecraft.client.renderer.block.model.BlockModelDefinition
+import net.minecraft.client.renderer.block.model.Variant
+import net.minecraft.core.registries.BuiltInRegistries
+import net.minecraft.resources.ResourceKey
+import net.minecraft.core.registries.Registries
+import net.minecraft.server.packs.resources.Resource
+import net.minecraft.server.packs.resources.ResourceManager
+import net.minecraft.server.packs.resources.SimplePreparableReloadListener
+import net.minecraft.world.level.block.state.StateDefinition
+import net.minecraft.resources.ResourceLocation
+import net.minecraft.core.BlockPos
+import net.minecraft.world.phys.AABB
+import net.minecraft.util.profiling.ProfilerFiller
+import net.minecraft.util.thread.ParallelMapTransform
import moe.nea.firmament.Firmament
import moe.nea.firmament.annotations.Subscribe
import moe.nea.firmament.events.EarlyResourceReloadEvent
import moe.nea.firmament.events.FinalizeResourceManagerEvent
import moe.nea.firmament.events.SkyblockServerUpdateEvent
+import moe.nea.firmament.features.debug.DebugLogger
+import moe.nea.firmament.features.texturepack.CustomBlockTextures.createBakedModels
import moe.nea.firmament.features.texturepack.CustomGlobalTextures.logger
+import moe.nea.firmament.util.ErrorUtil
import moe.nea.firmament.util.IdentifierSerializer
import moe.nea.firmament.util.MC
import moe.nea.firmament.util.SBData
@@ -43,249 +62,406 @@ 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(
+ @Serializable
+ data class CustomBlockOverride(
+ val modes: @Serializable(SingletonSerializableList::class) List<String>,
+ val area: List<Area>? = null,
+ val replacements: Map<ResourceLocation, Replacement>,
+ )
+
+ @Serializable(with = Replacement.Serializer::class)
+ data class Replacement(
+ val block: ResourceLocation,
+ val sound: ResourceLocation?,
+ ) {
+ fun replace(block: BlockState): BlockStateModel? {
+ blockStateMap?.let { return it[block] }
+ return blockModel
+ }
+
+ @Transient
+ lateinit var overridingBlock: Block
+
+ @Transient
+ val blockModelIdentifier get() = block.withPrefix("block/")
+
+ /**
+ * Guaranteed to be set after [BakedReplacements.modelBakingFuture] is complete, if [unbakedBlockStateMap] is set.
+ */
+ @Transient
+ var blockStateMap: Map<BlockState, BlockStateModel>? = null
+
+ @Transient
+ var unbakedBlockStateMap: Map<BlockState, BlockStateModel.UnbakedRoot>? = null
+
+ /**
+ * Guaranteed to be set after [BakedReplacements.modelBakingFuture] is complete. Prefer [blockStateMap] if present.
+ */
+ @Transient
+ lateinit var blockModel: BlockStateModel
+
+ @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(ResourceLocation.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)
- }
-
- 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()
- }
- })
- }
+ ) {
+ @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)
+ }
+
+ fun toBox(): AABB {
+ return AABB(
+ realMin.x.toDouble(),
+ realMin.y.toDouble(),
+ realMin.z.toDouble(),
+ (realMax.x + 1).toDouble(),
+ (realMax.y + 1).toDouble(),
+ (realMax.z + 1).toDouble()
+ )
+ }
+ }
+
+ data class LocationReplacements(
+ val lookup: Map<Block, List<BlockReplacement>>
+ ) {
+ init {
+ lookup.forEach { (block, replacements) ->
+ for (replacement in replacements) {
+ replacement.replacement.overridingBlock = block
+ }
+ }
+ }
+ }
+
+ 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>) {
+ /**
+ * Fulfilled by [createBakedModels] which is called during model baking. Once completed, all [Replacement.blockModel] will be set.
+ */
+ val modelBakingFuture = CompletableFuture<Unit>()
+
+ /**
+ * @returns a list of all [Replacement]s.
+ */
+ fun collectAllReplacements(): Sequence<Replacement> {
+ return data.values.asSequence()
+ .flatMap { it.lookup.values }
+ .flatten()
+ .map { it.replacement }
+ }
+ }
+
+ 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.viewArea?.sections?.forEach {
+ // false schedules rebuilds outside a 27 block radius to happen async
+ // nb: this sets the dirty but to true, the boolean parameter specifies the update behaviour
+ it.setDirty(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?): BlockStateModel? {
+ return getReplacement(block, blockPos)?.replace(block)
+ }
+
+ @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
+ @get:JvmStatic
+ 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
+ )
+ }
+
+ private fun prepare(manager: ResourceManager): BakedReplacements {
+ val resources = manager.listResources("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.open())
+ .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.lookupOrThrow(Registries.BLOCK)
+ .get(ResourceKey.create(Registries.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) })
+ }
+
+ @Subscribe
+ fun onStart(event: FinalizeResourceManagerEvent) {
+ event.resourceManager.registerReloadListener(object :
+ SimplePreparableReloadListener<BakedReplacements>() {
+ override fun prepare(manager: ResourceManager, profiler: ProfilerFiller): BakedReplacements {
+ return preparationFuture.join().also {
+ it.modelBakingFuture.join()
+ }
+ }
+
+ override fun apply(prepared: BakedReplacements, manager: ResourceManager, profiler: ProfilerFiller?) {
+ allLocationReplacements = prepared
+ refreshReplacements()
+ }
+ })
+ }
+
+ fun simpleBlockModel(blockId: ResourceLocation): SingleVariant.Unbaked {
+ // TODO: does this need to be shared between resolving and baking? I think not, but it would probably be wise to do so in the future.
+ return SingleVariant.Unbaked(
+ Variant(blockId)
+ )
+ }
+
+ /**
+ * Used by [moe.nea.firmament.init.SectionBuilderRiser]
+ */
+
+ @JvmStatic
+ fun patchIndigo(original: BlockStateModel, pos: BlockPos?, state: BlockState): BlockStateModel {
+ return getReplacementModel(state, pos) ?: original
+ }
+
+ @JvmStatic
+ fun collectExtraModels(modelsCollector: ModelDiscovery) {
+ preparationFuture.join().collectAllReplacements()
+ .forEach {
+ modelsCollector.addRoot(simpleBlockModel(it.blockModelIdentifier))
+ it.unbakedBlockStateMap?.values?.forEach {
+ modelsCollector.addRoot(it)
+ }
+ }
+ }
+
+ @JvmStatic
+ fun createBakedModels(baker: ModelBaker, executor: Executor): CompletableFuture<Void?> {
+ return preparationFuture.thenComposeAsync(Function { replacements ->
+ val allBlockStates = CompletableFuture.allOf(
+ *replacements.collectAllReplacements().filter { it.unbakedBlockStateMap != null }.map {
+ CompletableFuture.supplyAsync({
+ it.blockStateMap = it.unbakedBlockStateMap
+ ?.map {
+ it.key to it.value.bake(it.key, baker)
+ }
+ ?.toMap()
+ }, executor)
+ }.toList().toTypedArray()
+ )
+ val byModel = replacements.collectAllReplacements().groupBy { it.blockModelIdentifier }
+ val modelBakingTask = ParallelMapTransform.schedule(byModel, { blockId, replacements ->
+ val unbakedModel = SingleVariant.Unbaked(
+ Variant(blockId)
+ )
+ val baked = unbakedModel.bake(baker)
+ replacements.forEach {
+ it.blockModel = baked
+ }
+ }, executor)
+ modelBakingTask.thenComposeAsync {
+ allBlockStates
+ }.thenAcceptAsync {
+ replacements.modelBakingFuture.complete(Unit)
+ }
+ }, executor)
+ }
+
+ @JvmStatic
+ fun collectExtraBlockStateMaps(
+ extra: BakedReplacements,
+ original: Map<ResourceLocation, List<Resource>>,
+ stateManagers: Function<ResourceLocation, StateDefinition<Block, BlockState>?>
+ ) {
+ extra.collectAllReplacements().forEach {
+ val blockId = BuiltInRegistries.BLOCK.getResourceKey(it.overridingBlock).getOrNull()?.location() ?: return@forEach
+ val allModels = mutableListOf<BlockStateModelLoader.LoadedBlockModelDefinition>()
+ val stateManager = stateManagers.apply(blockId) ?: return@forEach
+ for (resource in original[BlockStateModelLoader.BLOCKSTATE_LISTER.idToFile(it.block)] ?: return@forEach) {
+ try {
+ resource.openAsReader().use { reader ->
+ val jsonElement = JsonParser.parseReader(reader)
+ val blockModelDefinition =
+ BlockModelDefinition.CODEC.parse(JsonOps.INSTANCE, jsonElement)
+ .getOrThrow { msg: String? -> JsonParseException(msg) }
+ allModels.add(
+ BlockStateModelLoader.LoadedBlockModelDefinition(
+ resource.sourcePackId(),
+ blockModelDefinition
+ )
+ )
+ }
+ } catch (exception: Exception) {
+ ErrorUtil.softError(
+ "Failed to load custom blockstate definition ${it.block} from pack ${resource.sourcePackId()}",
+ exception
+ )
+ }
+ }
+
+ try {
+ it.unbakedBlockStateMap = BlockStateModelLoader.loadBlockStateDefinitionStack(
+ blockId,
+ stateManager,
+ allModels
+ ).models
+ } catch (exception: Exception) {
+ ErrorUtil.softError("Failed to combine custom blockstate definitions for ${it.block}", exception)
+ }
+ }
+ }
}
diff --git a/src/texturePacks/java/moe/nea/firmament/features/texturepack/CustomBlockTexturesDebugger.kt b/src/texturePacks/java/moe/nea/firmament/features/texturepack/CustomBlockTexturesDebugger.kt
new file mode 100644
index 0000000..ba19e0f
--- /dev/null
+++ b/src/texturePacks/java/moe/nea/firmament/features/texturepack/CustomBlockTexturesDebugger.kt
@@ -0,0 +1,132 @@
+package moe.nea.firmament.features.texturepack
+
+import com.mojang.brigadier.arguments.IntegerArgumentType
+import net.fabricmc.fabric.api.client.command.v2.ClientCommandManager
+import net.minecraft.world.level.block.Block
+import net.minecraft.commands.arguments.blocks.BlockStateParser
+import net.minecraft.commands.arguments.blocks.BlockStateArgument
+import net.minecraft.world.phys.AABB
+import net.minecraft.world.phys.Vec3
+import moe.nea.firmament.annotations.Subscribe
+import moe.nea.firmament.commands.get
+import moe.nea.firmament.commands.thenArgument
+import moe.nea.firmament.commands.thenExecute
+import moe.nea.firmament.commands.thenLiteral
+import moe.nea.firmament.events.CommandEvent
+import moe.nea.firmament.events.WorldRenderLastEvent
+import moe.nea.firmament.features.debug.DeveloperFeatures
+import moe.nea.firmament.util.MC
+import moe.nea.firmament.util.render.RenderInWorldContext
+import moe.nea.firmament.util.tr
+
+object CustomBlockTexturesDebugger {
+ var debugMode: DebugMode = DebugMode.Never
+ var range = 30
+
+
+ @Subscribe
+ fun onRender(event: WorldRenderLastEvent) {
+ if (debugMode == DebugMode.Never) return
+ val replacements = CustomBlockTextures.currentIslandReplacements ?: return
+ RenderInWorldContext.renderInWorld(event) {
+ for ((block, repl) in replacements.lookup) {
+ if (!debugMode.shouldHighlight(block)) continue
+ for (i in repl) {
+ if (i.roughCheck != null)
+ tryRenderBox(i.roughCheck!!.toBox(), 0x50FF8050.toInt())
+ i.checks?.forEach { area ->
+ tryRenderBox(area.toBox(), 0x5050FF50.toInt())
+ }
+ }
+ }
+ }
+ }
+
+ fun RenderInWorldContext.tryRenderBox(box: AABB, colour: Int) {
+ val player = MC.player?.position ?: Vec3.ZERO
+ if (box.center.distanceTo(player) < range + maxOf(
+ box.zsize, box.xsize, box.ysize
+ ) / 2 && !box.contains(player)
+ ) {
+ box(box, colour)
+ }
+ }
+
+
+ @Subscribe
+ fun onCommand(event: CommandEvent.SubCommand) {
+ event.subcommand(DeveloperFeatures.DEVELOPER_SUBCOMMAND) {
+ thenLiteral("debugcbt") {
+ thenLiteral("range") {
+ thenArgument("range", IntegerArgumentType.integer(0)) { rangeArg ->
+ thenExecute {
+ this@CustomBlockTexturesDebugger.range = get(rangeArg)
+ MC.sendChat(
+ tr(
+ "firmament.debugcbt.always",
+ "Only render areas within ${this@CustomBlockTexturesDebugger.range} blocks"
+ )
+ )
+ }
+ }
+ }
+ thenLiteral("all") {
+ thenExecute {
+ debugMode = DebugMode.Always
+ MC.sendChat(
+ tr(
+ "firmament.debugcbt.always",
+ "Showing debug outlines for all custom block textures"
+ )
+ )
+ }
+ }
+ thenArgument("block", BlockStateArgument.block(event.commandRegistryAccess)) { block ->
+ thenExecute {
+ val block = get(block).state.block
+ debugMode = DebugMode.ForBlock(block)
+ MC.sendChat(
+ tr(
+ "firmament.debugcbt.block",
+ "Showing debug outlines for all custom ${block.name} textures"
+ )
+ )
+ }
+ }
+ thenLiteral("never") {
+ thenExecute {
+ debugMode = DebugMode.Never
+ MC.sendChat(
+ tr(
+ "firmament.debugcbt.disabled",
+ "Disabled debug outlines for custom block textures"
+ )
+ )
+ }
+ }
+ }
+ }
+ }
+
+ sealed interface DebugMode {
+ fun shouldHighlight(block: Block): Boolean
+
+ data object Never : DebugMode {
+ override fun shouldHighlight(block: Block): Boolean {
+ return false
+ }
+ }
+
+ data class ForBlock(val block: Block) : DebugMode {
+ override fun shouldHighlight(block: Block): Boolean {
+ return block == this.block
+ }
+ }
+
+ data object Always : DebugMode {
+ override fun shouldHighlight(block: Block): Boolean {
+ return true
+ }
+ }
+ }
+}
diff --git a/src/texturePacks/java/moe/nea/firmament/features/texturepack/CustomGlobalArmorOverrides.kt b/src/texturePacks/java/moe/nea/firmament/features/texturepack/CustomGlobalArmorOverrides.kt
index 85dfa32..7e3c018 100644
--- a/src/texturePacks/java/moe/nea/firmament/features/texturepack/CustomGlobalArmorOverrides.kt
+++ b/src/texturePacks/java/moe/nea/firmament/features/texturepack/CustomGlobalArmorOverrides.kt
@@ -8,16 +8,16 @@ 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 net.minecraft.client.resources.model.EquipmentClientInfo
+import net.minecraft.world.item.equipment.Equippable
+import net.minecraft.world.entity.EquipmentSlot
+import net.minecraft.world.item.ItemStack
+import net.minecraft.world.item.equipment.EquipmentAssets
+import net.minecraft.resources.ResourceKey
+import net.minecraft.server.packs.resources.ResourceManager
+import net.minecraft.server.packs.resources.SimplePreparableReloadListener
+import net.minecraft.resources.ResourceLocation
+import net.minecraft.util.profiling.ProfilerFiller
import moe.nea.firmament.Firmament
import moe.nea.firmament.annotations.Subscribe
import moe.nea.firmament.events.FinalizeResourceManagerEvent
@@ -30,14 +30,14 @@ import moe.nea.firmament.util.skyBlockId
object CustomGlobalArmorOverrides {
@Serializable
data class ArmorOverride(
- @SerialName("item_ids")
+ @SerialName("item_ids")
val itemIds: List<String>,
- val layers: List<ArmorOverrideLayer>? = null,
- val model: Identifier? = null,
- val overrides: List<ArmorOverrideOverride> = listOf(),
+ val layers: List<ArmorOverrideLayer>? = null,
+ val model: ResourceLocation? = null,
+ val overrides: List<ArmorOverrideOverride> = listOf(),
) {
@Transient
- lateinit var modelIdentifier: Identifier
+ lateinit var modelIdentifier: ResourceLocation
fun bake(manager: ResourceManager) {
modelIdentifier = bakeModel(model, layers)
overrides.forEach { it.bake(manager) }
@@ -51,16 +51,16 @@ object CustomGlobalArmorOverrides {
@Serializable
data class ArmorOverrideLayer(
- val tint: Boolean = false,
- val identifier: Identifier,
- val suffix: String = "",
+ val tint: Boolean = false,
+ val identifier: ResourceLocation,
+ val suffix: String = "",
)
@Serializable
data class ArmorOverrideOverride(
- val predicate: FirmamentModelPredicate,
- val layers: List<ArmorOverrideLayer>? = null,
- val model: Identifier? = null,
+ val predicate: FirmamentModelPredicate,
+ val layers: List<ArmorOverrideLayer>? = null,
+ val model: ResourceLocation? = null,
) {
init {
require(layers != null || model != null) { "Either model or layers must be specified for armor override override" }
@@ -68,60 +68,69 @@ object CustomGlobalArmorOverrides {
}
@Transient
- lateinit var modelIdentifier: Identifier
+ lateinit var modelIdentifier: ResourceLocation
fun bake(manager: ResourceManager) {
modelIdentifier = bakeModel(model, layers)
}
}
- private fun resolveComponent(slot: EquipmentSlot, model: Identifier): EquippableComponent {
- return EquippableComponent(
+ private fun resolveComponent(slot: EquipmentSlot, model: ResourceLocation): Equippable {
+ return Equippable(
slot,
null,
- Optional.of(RegistryKey.of(EquipmentAssetKeys.REGISTRY_KEY, model)),
+ Optional.of(ResourceKey.create(EquipmentAssets.ROOT_ID, model)),
Optional.empty(),
- Optional.empty(), false, false, false
+ Optional.empty(),
+ false,
+ false,
+ false,
+ false,
+ false,
+ null
)
}
+ // TODO: BipedEntityRenderer.getEquippedStack create copies of itemstacks for rendering. This means this cache is essentially useless
+ // If i figure out how to circumvent this (maybe track the origin of those copied itemstacks in some sort of variable in the itemstack to track back the original instance) i should reenable this cache.
+ // Then also re add this to the cache clearing function
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()
+ WeakCache.dontMemoize<ItemStack, EquipmentSlot, Optional<Equippable>>("ArmorOverrides") { stack, slot ->
+ val id = stack.skyBlockId ?: return@dontMemoize Optional.empty()
+ val override = overrides[id.neuItem] ?: return@dontMemoize Optional.empty()
for (suboverride in override.overrides) {
if (suboverride.predicate.test(stack)) {
- return@memoize resolveComponent(slot, suboverride.modelIdentifier).intoOptional()
+ return@dontMemoize resolveComponent(slot, suboverride.modelIdentifier).intoOptional()
}
}
- return@memoize resolveComponent(slot, override.modelIdentifier).intoOptional()
+ return@dontMemoize resolveComponent(slot, override.modelIdentifier).intoOptional()
}
var overrides: Map<String, ArmorOverride> = mapOf()
- private var bakedOverrides: MutableMap<Identifier, EquipmentModel> = mutableMapOf()
+ private var bakedOverrides: MutableMap<ResourceLocation, EquipmentClientInfo> = mutableMapOf()
private val sentinelFirmRunning = AtomicInteger()
- private fun bakeModel(model: Identifier?, layers: List<ArmorOverrideLayer>?): Identifier {
+ private fun bakeModel(model: ResourceLocation?, layers: List<ArmorOverrideLayer>?): ResourceLocation {
require(model == null || layers == null)
if (model != null) {
return model
} else if (layers != null) {
val idNumber = sentinelFirmRunning.incrementAndGet()
- val identifier = Identifier.of("firmament:sentinel/armor/$idNumber")
+ val identifier = ResourceLocation.parse("firmament:sentinel/armor/$idNumber")
val equipmentLayers = layers.map {
- EquipmentModel.Layer(
+ EquipmentClientInfo.Layer(
it.identifier, if (it.tint) {
- Optional.of(EquipmentModel.Dyeable(Optional.empty()))
+ Optional.of(EquipmentClientInfo.Dyeable(Optional.of(0xFFA06540.toInt())))
} else {
Optional.empty()
},
false
)
}
- bakedOverrides[identifier] = EquipmentModel(
+ bakedOverrides[identifier] = EquipmentClientInfo(
mapOf(
- EquipmentModel.LayerType.HUMANOID to equipmentLayers,
- EquipmentModel.LayerType.HUMANOID_LEGGINGS to equipmentLayers,
+ EquipmentClientInfo.LayerType.HUMANOID to equipmentLayers,
+ EquipmentClientInfo.LayerType.HUMANOID_LEGGINGS to equipmentLayers,
)
)
return identifier
@@ -133,38 +142,40 @@ object CustomGlobalArmorOverrides {
@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") {
+ event.resourceManager.registerReloadListener(object :
+ SimplePreparableReloadListener<Map<String, ArmorOverride>>() {
+ override fun prepare(manager: ResourceManager, profiler: ProfilerFiller): Map<String, ArmorOverride> {
+ val overrideFiles = manager.listResources("overrides/armor_models") {
it.namespace == "firmskyblock" && it.path.endsWith(".json")
}
val overrides = overrideFiles.mapNotNull {
- Firmament.tryDecodeJsonFromStream<ArmorOverride>(it.value.inputStream).getOrElse { ex ->
+ Firmament.tryDecodeJsonFromStream<ArmorOverride>(it.value.open()).getOrElse { ex ->
logger.error("Failed to load armor texture override at ${it.key}", ex)
null
}
}
+ bakedOverrides.clear()
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()
+ override fun apply(prepared: Map<String, ArmorOverride>, manager: ResourceManager, profiler: ProfilerFiller) {
overrides = prepared
}
})
}
@JvmStatic
- fun overrideArmor(itemStack: ItemStack, slot: EquipmentSlot): Optional<EquippableComponent> {
+ fun overrideArmor(itemStack: ItemStack, slot: EquipmentSlot): Optional<Equippable> {
+ if (!CustomSkyBlockTextures.TConfig.enableArmorOverrides) return Optional.empty()
return overrideCache.invoke(itemStack, slot)
}
@JvmStatic
- fun overrideArmorLayer(id: Identifier): EquipmentModel? {
+ fun overrideArmorLayer(id: ResourceLocation): EquipmentClientInfo? {
+ if (!CustomSkyBlockTextures.TConfig.enableArmorOverrides) return null
return bakedOverrides[id]
}
diff --git a/src/texturePacks/java/moe/nea/firmament/features/texturepack/CustomGlobalTextures.kt b/src/texturePacks/java/moe/nea/firmament/features/texturepack/CustomGlobalTextures.kt
index 02c0714..60a6c06 100644
--- a/src/texturePacks/java/moe/nea/firmament/features/texturepack/CustomGlobalTextures.kt
+++ b/src/texturePacks/java/moe/nea/firmament/features/texturepack/CustomGlobalTextures.kt
@@ -8,40 +8,33 @@ 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 net.minecraft.server.packs.resources.ResourceManager
+import net.minecraft.server.packs.resources.SimplePreparableReloadListener
+import net.minecraft.network.chat.Component
+import net.minecraft.resources.ResourceLocation
+import net.minecraft.util.profiling.ProfilerFiller
import moe.nea.firmament.Firmament
import moe.nea.firmament.annotations.Subscribe
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
-
+object CustomGlobalTextures : SimplePreparableReloadListener<CustomGlobalTextures.CustomGuiTextureOverride>() {
class CustomGuiTextureOverride(
val classes: List<ItemOverrideCollection>
)
@Serializable
data class GlobalItemOverride(
- val screen: @Serializable(SingletonSerializableList::class) List<Identifier>,
- val model: Identifier,
- val predicate: FirmamentModelPredicate,
+ val screen: @Serializable(SingletonSerializableList::class) List<ResourceLocation>,
+ val model: ResourceLocation,
+ val predicate: FirmamentModelPredicate,
)
@Serializable
@@ -56,7 +49,7 @@ object CustomGlobalTextures : SinglePreparationResourceReloader<CustomGlobalText
@Subscribe
fun onStart(event: FinalizeResourceManagerEvent) {
- MC.resourceManager.registerReloader(this)
+ MC.resourceManager.registerReloadListener(this)
}
@Subscribe
@@ -65,27 +58,29 @@ object CustomGlobalTextures : SinglePreparationResourceReloader<CustomGlobalText
.supplyAsync(
{
prepare(event.resourceManager)
- }, event.preparationExecutor)
+ }, event.preparationExecutor
+ )
}
@Volatile
var preparationFuture: CompletableFuture<CustomGuiTextureOverride> = CompletableFuture.completedFuture(
- CustomGuiTextureOverride(listOf()))
+ CustomGuiTextureOverride(listOf())
+ )
- override fun prepare(manager: ResourceManager?, profiler: Profiler?): CustomGuiTextureOverride {
+ override fun prepare(manager: ResourceManager?, profiler: ProfilerFiller?): CustomGuiTextureOverride {
return preparationFuture.join()
}
- override fun apply(prepared: CustomGuiTextureOverride, manager: ResourceManager?, profiler: Profiler?) {
+ override fun apply(prepared: CustomGuiTextureOverride, manager: ResourceManager?, profiler: ProfilerFiller?) {
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") }
+ manager.listResources("overrides/item") { it.namespace == "firmskyblock" && it.path.endsWith(".json") }
.mapNotNull {
- Firmament.tryDecodeJsonFromStream<GlobalItemOverride>(it.value.inputStream).getOrElse { ex ->
+ Firmament.tryDecodeJsonFromStream<GlobalItemOverride>(it.value.open()).getOrElse { ex ->
ErrorUtil.softError("Failed to load global item override at ${it.key}", ex)
null
}
@@ -97,15 +92,18 @@ object CustomGlobalTextures : SinglePreparationResourceReloader<CustomGlobalText
.mapNotNull {
val key = it.key
val guiClassResource =
- manager.getResource(Identifier.of(key.namespace, "filters/screen/${key.path}.json"))
+ manager.getResource(ResourceLocation.fromNamespaceAndPath(key.namespace, "filters/screen/${key.path}.json"))
.getOrNull()
?: return@mapNotNull runNull {
- ErrorUtil.softError("Failed to locate screen filter at $key")
+ ErrorUtil.softError("Failed to locate screen filter at $key used by ${it.value.map { it.first }}")
}
val screenFilter =
- Firmament.tryDecodeJsonFromStream<ScreenFilter>(guiClassResource.inputStream)
+ Firmament.tryDecodeJsonFromStream<ScreenFilter>(guiClassResource.open())
.getOrElse { ex ->
- ErrorUtil.softError("Failed to load screen filter at $key", ex)
+ ErrorUtil.softError(
+ "Failed to load screen filter at $key used by ${it.value.map { it.first }}",
+ ex
+ )
return@mapNotNull null
}
ItemOverrideCollection(screenFilter, it.value.map { it.second })
@@ -120,7 +118,7 @@ object CustomGlobalTextures : SinglePreparationResourceReloader<CustomGlobalText
@Subscribe
fun onOpenGui(event: ScreenChangeEvent) {
- val newTitle = event.new?.title ?: Text.empty()
+ val newTitle = event.new?.title ?: Component.empty()
matchingOverrides = guiClassOverrides.classes
.filterTo(mutableSetOf()) { it.screenFilter.title.matches(newTitle) }
}
diff --git a/src/texturePacks/java/moe/nea/firmament/features/texturepack/CustomModelOverrideParser.kt b/src/texturePacks/java/moe/nea/firmament/features/texturepack/CustomModelOverrideParser.kt
index fca8944..2d615c2 100644
--- a/src/texturePacks/java/moe/nea/firmament/features/texturepack/CustomModelOverrideParser.kt
+++ b/src/texturePacks/java/moe/nea/firmament/features/texturepack/CustomModelOverrideParser.kt
@@ -7,9 +7,9 @@ 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 net.minecraft.client.renderer.item.ItemModels
+import net.minecraft.world.item.ItemStack
+import net.minecraft.resources.ResourceLocation
import moe.nea.firmament.Firmament
import moe.nea.firmament.annotations.Subscribe
import moe.nea.firmament.events.FinalizeResourceManagerEvent
@@ -17,11 +17,14 @@ import moe.nea.firmament.features.texturepack.predicates.AndPredicate
import moe.nea.firmament.features.texturepack.predicates.CastPredicate
import moe.nea.firmament.features.texturepack.predicates.DisplayNamePredicate
import moe.nea.firmament.features.texturepack.predicates.ExtraAttributesPredicate
+import moe.nea.firmament.features.texturepack.predicates.GenericComponentPredicate
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.features.texturepack.predicates.PullingPredicate
+import moe.nea.firmament.features.texturepack.predicates.SkullPredicate
import moe.nea.firmament.util.json.KJsonOps
object CustomModelOverrideParser {
@@ -46,11 +49,11 @@ object CustomModelOverrideParser {
}
)
- val predicateParsers = mutableMapOf<Identifier, FirmamentModelPredicateParser>()
+ val predicateParsers = mutableMapOf<ResourceLocation, FirmamentModelPredicateParser>()
fun registerPredicateParser(name: String, parser: FirmamentModelPredicateParser) {
- predicateParsers[Identifier.of("firmament", name)] = parser
+ predicateParsers[ResourceLocation.fromNamespaceAndPath("firmament", name)] = parser
}
init {
@@ -62,6 +65,8 @@ object CustomModelOverrideParser {
registerPredicateParser("item", ItemPredicate.Parser)
registerPredicateParser("extra_attributes", ExtraAttributesPredicate.Parser)
registerPredicateParser("pet", PetPredicate.Parser)
+ registerPredicateParser("component", GenericComponentPredicate.Parser)
+ registerPredicateParser("skull", SkullPredicate.Parser)
}
private val neverPredicate = listOf(
@@ -79,8 +84,14 @@ object CustomModelOverrideParser {
if (predicateName == "cast") { // 1.21.4
parsedPredicates.add(CastPredicate.Parser.parse(predicates[predicateName]) ?: return neverPredicate)
}
+ if (predicateName == "pull") {
+ parsedPredicates.add(PullingPredicate.Parser.parse(predicates[predicateName]) ?: return neverPredicate)
+ }
+ if (predicateName == "pulling") {
+ parsedPredicates.add(PullingPredicate.AnyPulling)
+ }
if (!predicateName.startsWith("firmament:")) continue
- val identifier = Identifier.of(predicateName)
+ val identifier = ResourceLocation.parse(predicateName)
val parser = predicateParsers[identifier] ?: return neverPredicate
val parsedPredicate = parser.parse(predicates[predicateName]) ?: return neverPredicate
parsedPredicates.add(parsedPredicate)
@@ -99,10 +110,14 @@ object CustomModelOverrideParser {
@Subscribe
fun finalizeResources(event: FinalizeResourceManagerEvent) {
- ItemModelTypes.ID_MAPPER.put(
+ ItemModels.ID_MAPPER.put(
Firmament.identifier("predicates/legacy"),
PredicateModel.Unbaked.CODEC
)
+ ItemModels.ID_MAPPER.put(
+ Firmament.identifier("head_model"),
+ HeadModelChooser.Unbaked.CODEC
+ )
}
}
diff --git a/src/texturePacks/java/moe/nea/firmament/features/texturepack/CustomScreenLayouts.kt b/src/texturePacks/java/moe/nea/firmament/features/texturepack/CustomScreenLayouts.kt
new file mode 100644
index 0000000..e026365
--- /dev/null
+++ b/src/texturePacks/java/moe/nea/firmament/features/texturepack/CustomScreenLayouts.kt
@@ -0,0 +1,257 @@
+package moe.nea.firmament.features.texturepack
+
+import kotlinx.serialization.SerialName
+import kotlinx.serialization.Serializable
+import kotlinx.serialization.Transient
+import net.minecraft.client.gui.Font
+import net.minecraft.client.renderer.RenderPipelines
+import net.minecraft.client.gui.GuiGraphics
+import net.minecraft.client.gui.screens.Screen
+import net.minecraft.client.gui.screens.inventory.AbstractSignEditScreen
+import net.minecraft.client.gui.screens.inventory.AbstractContainerScreen
+import net.minecraft.client.gui.screens.inventory.HangingSignEditScreen
+import net.minecraft.client.gui.screens.inventory.SignEditScreen
+import net.minecraft.client.renderer.RenderType
+import net.minecraft.core.registries.BuiltInRegistries
+import net.minecraft.server.packs.resources.ResourceManager
+import net.minecraft.server.packs.resources.SimplePreparableReloadListener
+import net.minecraft.world.inventory.AbstractContainerMenu
+import net.minecraft.world.inventory.Slot
+import net.minecraft.network.chat.Component
+import net.minecraft.resources.ResourceLocation
+import net.minecraft.util.profiling.ProfilerFiller
+import moe.nea.firmament.Firmament
+import moe.nea.firmament.annotations.Subscribe
+import moe.nea.firmament.events.FinalizeResourceManagerEvent
+import moe.nea.firmament.events.ScreenChangeEvent
+import moe.nea.firmament.features.texturepack.CustomScreenLayouts.Alignment.CENTER
+import moe.nea.firmament.features.texturepack.CustomScreenLayouts.Alignment.LEFT
+import moe.nea.firmament.features.texturepack.CustomScreenLayouts.Alignment.RIGHT
+import moe.nea.firmament.mixins.accessor.AccessorHandledScreen
+import moe.nea.firmament.mixins.accessor.AccessorScreenHandler
+import moe.nea.firmament.util.ErrorUtil.intoCatch
+import moe.nea.firmament.util.IdentifierSerializer
+import moe.nea.firmament.util.accessors.castAccessor
+
+object CustomScreenLayouts : SimplePreparableReloadListener<List<CustomScreenLayouts.CustomScreenLayout>>() {
+
+ @Serializable
+ data class CustomScreenLayout(
+ val predicates: Preds,
+ val background: BackgroundReplacer? = null,
+ val slots: List<SlotReplacer> = listOf(),
+ val playerTitle: TitleReplacer? = null,
+ val containerTitle: TitleReplacer? = null,
+ val repairCostTitle: TitleReplacer? = null,
+ val nameField: ComponentMover? = null,
+ val signLines: List<ComponentMover>? = null,
+ ) {
+ init {
+ if (signLines != null)
+ require(signLines.size == 4)
+ }
+ }
+
+ @Serializable
+ data class ComponentMover(
+ val x: Int,
+ val y: Int,
+ val width: Int? = null,
+ val height: Int? = null,
+ )
+
+ @Serializable
+ data class Preds(
+ val label: StringMatcher,
+ @Serializable(with = IdentifierSerializer::class)
+ val screenType: ResourceLocation? = null,
+ ) {
+ fun matches(screen: Screen): Boolean {
+ // TODO: does this deserve the restriction to handled screen
+ val type = when (screen) {
+ is AbstractContainerScreen<*> -> (screen.menu as AccessorScreenHandler).type_firmament?.let {
+ BuiltInRegistries.MENU.getKey(it)
+ }
+
+ is HangingSignEditScreen -> ResourceLocation.fromNamespaceAndPath("firmskyblock", "hanging_sign")
+ is SignEditScreen -> ResourceLocation.fromNamespaceAndPath("firmskyblock", "sign")
+ else -> null
+ }
+ val typeMatches = screenType == null || type == screenType;
+ return label.matches(screen.title) && typeMatches
+ }
+ }
+
+ @Serializable
+ data class BackgroundReplacer(
+ @Serializable(with = IdentifierSerializer::class)
+ val texture: ResourceLocation,
+ // TODO: allow selectively still rendering some components (recipe button, trade backgrounds, furnace flame progress, arrows)
+ val x: Int,
+ val y: Int,
+ val width: Int,
+ val height: Int,
+ ) {
+ fun renderDirect(context: GuiGraphics) {
+ context.blit(
+ RenderPipelines.GUI_TEXTURED,
+ this.texture,
+ this.x, this.y,
+ 0F, 0F,
+ this.width, this.height, this.width, this.height,
+ )
+ }
+
+ fun renderGeneric(context: GuiGraphics, screen: AbstractContainerScreen<*>) {
+ screen.castAccessor()
+ val originalX: Int = (screen.width - screen.backgroundWidth_Firmament) / 2
+ val originalY: Int = (screen.height - screen.backgroundHeight_Firmament) / 2
+ val modifiedX = originalX + this.x
+ val modifiedY = originalY + this.y
+ val textureWidth = this.width
+ val textureHeight = this.height
+ context.blit(
+ RenderPipelines.GUI_TEXTURED,
+ this.texture,
+ modifiedX,
+ modifiedY,
+ 0.0f,
+ 0.0f,
+ textureWidth,
+ textureHeight,
+ textureWidth,
+ textureHeight
+ )
+
+ }
+ }
+
+ @Serializable
+ data class SlotReplacer(
+ // TODO: override getRecipeBookButtonPos as well
+ // TODO: is this index or id (i always forget which one is duplicated per inventory)
+ val index: Int,
+ val x: Int,
+ val y: Int,
+ ) {
+ fun move(slots: List<Slot>) {
+ val slot = slots.getOrNull(index) ?: return
+ slot.x = x
+ slot.y = y
+ }
+ }
+
+ @Serializable
+ enum class Alignment {
+ @SerialName("left")
+ LEFT,
+
+ @SerialName("center")
+ CENTER,
+
+ @SerialName("right")
+ RIGHT
+ }
+
+ @Serializable
+ data class TitleReplacer(
+ val x: Int? = null,
+ val y: Int? = null,
+ val align: Alignment = Alignment.LEFT,
+ val replace: String? = null
+ ) {
+ @Transient
+ val replacedText: Component? = replace?.let(Component::literal)
+
+ fun replaceText(text: Component): Component {
+ if (replacedText != null) return replacedText
+ return text
+ }
+
+ fun replaceY(y: Int): Int {
+ return this.y ?: y
+ }
+
+ fun replaceX(font: Font, text: Component, x: Int): Int {
+ val baseX = this.x ?: x
+ return baseX + when (this.align) {
+ LEFT -> 0
+ CENTER -> -font.width(text) / 2
+ RIGHT -> -font.width(text)
+ }
+ }
+
+ /**
+ * Not technically part of the package, but it does allow for us to later on seamlessly integrate a color option into this class as well
+ */
+ fun replaceColor(text: Component, color: Int): Int {
+ return CustomTextColors.mapTextColor(text, color)
+ }
+ }
+
+
+ @Subscribe
+ fun onStart(event: FinalizeResourceManagerEvent) {
+ event.resourceManager.registerReloadListener(CustomScreenLayouts)
+ }
+
+ override fun prepare(
+ manager: ResourceManager,
+ profiler: ProfilerFiller
+ ): List<CustomScreenLayout> {
+ val allScreenLayouts = manager.listResources(
+ "overrides/screen_layout",
+ { it.path.endsWith(".json") && it.namespace == "firmskyblock" })
+ val allParsedLayouts = allScreenLayouts.mapNotNull { (path, stream) ->
+ Firmament.tryDecodeJsonFromStream<CustomScreenLayout>(stream.open())
+ .intoCatch("Could not read custom screen layout from $path").orNull()
+ }
+ return allParsedLayouts
+ }
+
+ var customScreenLayouts = listOf<CustomScreenLayout>()
+
+ override fun apply(
+ prepared: List<CustomScreenLayout>,
+ manager: ResourceManager?,
+ profiler: ProfilerFiller?
+ ) {
+ this.customScreenLayouts = prepared
+ }
+
+ @get:JvmStatic
+ var activeScreenOverride = null as CustomScreenLayout?
+
+ val DO_NOTHING_TEXT_REPLACER = TitleReplacer()
+
+ @JvmStatic
+ fun <T> getMover(selector: (CustomScreenLayout) -> (T?)) =
+ activeScreenOverride?.let(selector)
+
+ @JvmStatic
+ fun getSignTextMover(index: Int) =
+ getMover { it.signLines?.get(index) }
+
+ @JvmStatic
+ fun getTextMover(selector: (CustomScreenLayout) -> (TitleReplacer?)) =
+ getMover(selector) ?: DO_NOTHING_TEXT_REPLACER
+
+ @Subscribe
+ fun onScreenOpen(event: ScreenChangeEvent) {
+ if (!CustomSkyBlockTextures.TConfig.allowLayoutChanges) {
+ activeScreenOverride = null
+ return
+ }
+ activeScreenOverride = event.new?.let { screen ->
+ customScreenLayouts.find { it.predicates.matches(screen) }
+ }
+
+ val screen = event.new as? AbstractContainerScreen<*> ?: return
+ val handler = screen.menu
+ activeScreenOverride?.let { override ->
+ override.slots.forEach { slotReplacer ->
+ slotReplacer.move(handler.slots)
+ }
+ }
+ }
+}
diff --git a/src/texturePacks/java/moe/nea/firmament/features/texturepack/CustomSkyBlockTextures.kt b/src/texturePacks/java/moe/nea/firmament/features/texturepack/CustomSkyBlockTextures.kt
index d9ca5b4..a324402 100644
--- a/src/texturePacks/java/moe/nea/firmament/features/texturepack/CustomSkyBlockTextures.kt
+++ b/src/texturePacks/java/moe/nea/firmament/features/texturepack/CustomSkyBlockTextures.kt
@@ -3,29 +3,29 @@ 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 net.minecraft.world.level.block.SkullBlock
+import net.minecraft.client.Minecraft
+import net.minecraft.client.renderer.RenderType
+import net.minecraft.world.item.component.ResolvableProfile
+import net.minecraft.resources.ResourceLocation
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.data.Config
+import moe.nea.firmament.util.data.ManagedConfig
import moe.nea.firmament.util.mc.decodeProfileTextureProperty
import moe.nea.firmament.util.skyBlockId
-object CustomSkyBlockTextures : FirmamentFeature {
- override val identifier: String
+object CustomSkyBlockTextures {
+ val identifier: String
get() = "custom-skyblock-textures"
+ @Config
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 }
@@ -34,17 +34,17 @@ object CustomSkyBlockTextures : FirmamentFeature {
val enableModelOverrides by toggle("model-overrides") { true }
val enableArmorOverrides by toggle("armor-overrides") { true }
val enableBlockOverrides by toggle("block-overrides") { true }
+ val enableLegacyMinecraftCompat by toggle("legacy-minecraft-path-support") { true }
val enableLegacyCIT by toggle("legacy-cit") { true }
val allowRecoloringUiText by toggle("recolor-text") { true }
+ val allowLayoutChanges by toggle("screen-layouts") { true }
}
- override val config: ManagedConfig
- get() = TConfig
-
val allItemCaches by lazy {
listOf(
skullTextureCache.cache,
- CustomGlobalArmorOverrides.overrideCache.cache
+ CustomItemModelEvent.cache.cache,
+ // TODO: re-add this once i figure out how to make the cache useful again CustomGlobalArmorOverrides.overrideCache.cache
)
}
@@ -75,13 +75,13 @@ object CustomSkyBlockTextures : FirmamentFeature {
fun onCustomModelId(it: CustomItemModelEvent) {
if (!TConfig.enabled) return
val id = it.itemStack.skyBlockId ?: return
- it.overrideIfExists(Identifier.of("firmskyblock", id.identifier.path))
+ it.overrideIfEmpty(ResourceLocation.fromNamespaceAndPath("firmskyblock", id.identifier.path))
}
private val skullTextureCache =
- WeakCache.memoize<ProfileComponent, Optional<Identifier>>("SkullTextureCache") { component ->
+ WeakCache.memoize<ResolvableProfile, Optional<ResourceLocation>>("SkullTextureCache") { component ->
val id = getSkullTexture(component) ?: return@memoize Optional.empty()
- if (!MinecraftClient.getInstance().resourceManager.getResource(id).isPresent) {
+ if (!Minecraft.getInstance().resourceManager.getResource(id).isPresent) {
return@memoize Optional.empty()
}
return@memoize Optional.of(id)
@@ -97,21 +97,21 @@ object CustomSkyBlockTextures : FirmamentFeature {
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 getSkullTexture(profile: ResolvableProfile): ResourceLocation? {
+ val id = getSkullId(profile.partialProfile().properties["textures"].firstOrNull() ?: return null) ?: return null
+ return ResourceLocation.fromNamespaceAndPath("firmskyblock", "textures/placedskull/$id.png")
}
fun modifySkullTexture(
- type: SkullBlock.SkullType?,
- component: ProfileComponent?,
- cir: CallbackInfoReturnable<RenderLayer>
+ type: SkullBlock.Type?,
+ component: ResolvableProfile?,
+ cir: CallbackInfoReturnable<RenderType>
) {
- if (type != SkullBlock.Type.PLAYER) return
+ if (type != SkullBlock.Types.PLAYER) return
if (!TConfig.skullsEnabled) return
if (component == null) return
val n = skullTextureCache.invoke(component).getOrNull() ?: return
- cir.returnValue = RenderLayer.getEntityTranslucent(n)
+ cir.returnValue = RenderType.entityTranslucent(n)
}
}
diff --git a/src/texturePacks/java/moe/nea/firmament/features/texturepack/CustomTextColors.kt b/src/texturePacks/java/moe/nea/firmament/features/texturepack/CustomTextColors.kt
index 4ca1796..5f3b08d 100644
--- a/src/texturePacks/java/moe/nea/firmament/features/texturepack/CustomTextColors.kt
+++ b/src/texturePacks/java/moe/nea/firmament/features/texturepack/CustomTextColors.kt
@@ -2,52 +2,66 @@ package moe.nea.firmament.features.texturepack
import java.util.Optional
import kotlinx.serialization.Serializable
+import kotlinx.serialization.Transient
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 net.minecraft.server.packs.resources.ResourceManager
+import net.minecraft.server.packs.resources.SimplePreparableReloadListener
+import net.minecraft.network.chat.Component
+import net.minecraft.resources.ResourceLocation
+import net.minecraft.util.profiling.ProfilerFiller
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?>() {
+object CustomTextColors : SimplePreparableReloadListener<CustomTextColors.TextOverrides?>() {
@Serializable
data class TextOverrides(
val defaultColor: Int,
val overrides: List<TextOverride> = listOf()
- )
+ ) {
+ /**
+ * Stub custom text color to allow always returning a text override
+ */
+ @Transient
+ val baseOverride = TextOverride(
+ StringMatcher.Equals("", false),
+ defaultColor,
+ 0,
+ 0
+ )
+ }
@Serializable
data class TextOverride(
val predicate: StringMatcher,
val override: Int,
+ val x: Int = 0,
+ val y: Int = 0,
)
@Subscribe
fun registerTextColorReloader(event: FinalizeResourceManagerEvent) {
- event.resourceManager.registerReloader(this)
+ event.resourceManager.registerReloadListener(this)
}
- val cache = WeakCache.memoize<Text, Optional<Int>>("CustomTextColor") { text ->
+ val cache = WeakCache.memoize<Component, Optional<TextOverride>>("CustomTextColor") { text ->
val override = textOverrides ?: return@memoize Optional.empty()
- Optional.of(override.overrides.find { it.predicate.matches(text) }?.override ?: override.defaultColor)
+ Optional.ofNullable(override.overrides.find { it.predicate.matches(text) })
}
- fun mapTextColor(text: Text, oldColor: Int): Int {
- if (textOverrides == null) return oldColor
- return cache(text).getOrNull() ?: oldColor
+ fun mapTextColor(text: Component, oldColor: Int): Int {
+ val override = cache(text).orElse(null)
+ return override?.override ?: textOverrides?.defaultColor ?: oldColor
}
override fun prepare(
- manager: ResourceManager,
- profiler: Profiler
+ manager: ResourceManager,
+ profiler: ProfilerFiller
): TextOverrides? {
- val resource = manager.getResource(Identifier.of("firmskyblock", "overrides/text_colors.json")).getOrNull()
+ val resource = manager.getResource(ResourceLocation.fromNamespaceAndPath("firmskyblock", "overrides/text_colors.json")).getOrNull()
?: return null
- return Firmament.tryDecodeJsonFromStream<TextOverrides>(resource.inputStream)
+ return Firmament.tryDecodeJsonFromStream<TextOverrides>(resource.open())
.getOrElse {
Firmament.logger.error("Could not parse text_colors.json", it)
null
@@ -57,9 +71,9 @@ object CustomTextColors : SinglePreparationResourceReloader<CustomTextColors.Tex
var textOverrides: TextOverrides? = null
override fun apply(
- prepared: TextOverrides?,
- manager: ResourceManager,
- profiler: Profiler
+ prepared: TextOverrides?,
+ manager: ResourceManager,
+ profiler: ProfilerFiller
) {
textOverrides = prepared
}
diff --git a/src/texturePacks/java/moe/nea/firmament/features/texturepack/CustomTextReplacements.kt b/src/texturePacks/java/moe/nea/firmament/features/texturepack/CustomTextReplacements.kt
new file mode 100644
index 0000000..71617fd
--- /dev/null
+++ b/src/texturePacks/java/moe/nea/firmament/features/texturepack/CustomTextReplacements.kt
@@ -0,0 +1,56 @@
+package moe.nea.firmament.features.texturepack
+
+import net.minecraft.server.packs.resources.ResourceManager
+import net.minecraft.server.packs.resources.SimplePreparableReloadListener
+import net.minecraft.network.chat.Component
+import net.minecraft.util.profiling.ProfilerFiller
+import moe.nea.firmament.Firmament
+import moe.nea.firmament.annotations.Subscribe
+import moe.nea.firmament.events.FinalizeResourceManagerEvent
+import moe.nea.firmament.util.ErrorUtil.intoCatch
+
+object CustomTextReplacements : SimplePreparableReloadListener<List<TreeishTextReplacer>>() {
+
+ override fun prepare(
+ manager: ResourceManager,
+ profiler: ProfilerFiller
+ ): List<TreeishTextReplacer> {
+ return manager.listResources("overrides/texts") { it.namespace == "firmskyblock" && it.path.endsWith(".json") }
+ .mapNotNull {
+ Firmament.tryDecodeJsonFromStream<TreeishTextReplacer>(it.value.open())
+ .intoCatch("Failed to load text override from ${it.key}").orNull()
+ }
+ }
+
+ var textReplacers: List<TreeishTextReplacer> = listOf()
+
+ override fun apply(
+ prepared: List<TreeishTextReplacer>,
+ manager: ResourceManager,
+ profiler: ProfilerFiller
+ ) {
+ this.textReplacers = prepared
+ }
+
+ @JvmStatic
+ fun replaceTexts(texts: List<Component>): List<Component> {
+ return texts.map { replaceText(it) }
+ }
+
+ @JvmStatic
+ fun replaceText(text: Component): Component {
+ // TODO: add a config option for this
+ val rawText = text.string
+ var text = text
+ for (replacer in textReplacers) {
+ if (!replacer.match.matches(rawText)) continue
+ text = replacer.replaceText(text)
+ }
+ return text
+ }
+
+ @Subscribe
+ fun onReloadStart(event: FinalizeResourceManagerEvent) {
+ event.resourceManager.registerReloadListener(this)
+ }
+}
diff --git a/src/texturePacks/java/moe/nea/firmament/features/texturepack/FirmamentModelPredicate.kt b/src/texturePacks/java/moe/nea/firmament/features/texturepack/FirmamentModelPredicate.kt
index d11fec0..d10b6e9 100644
--- a/src/texturePacks/java/moe/nea/firmament/features/texturepack/FirmamentModelPredicate.kt
+++ b/src/texturePacks/java/moe/nea/firmament/features/texturepack/FirmamentModelPredicate.kt
@@ -1,8 +1,11 @@
-
package moe.nea.firmament.features.texturepack
-import net.minecraft.item.ItemStack
+import kotlinx.serialization.Serializable
+import net.minecraft.world.entity.LivingEntity
+import net.minecraft.world.item.ItemStack
+@Serializable(with = FirmamentRootPredicateSerializer::class)
interface FirmamentModelPredicate {
- fun test(stack: ItemStack): Boolean
+ fun test(stack: ItemStack, holder: LivingEntity?): Boolean = test(stack)
+ fun test(stack: ItemStack): Boolean = test(stack, null)
}
diff --git a/src/texturePacks/java/moe/nea/firmament/features/texturepack/FirmamentRootPredicateSerializer.kt b/src/texturePacks/java/moe/nea/firmament/features/texturepack/FirmamentRootPredicateSerializer.kt
index 0b8ae8e..39e1fc3 100644
--- a/src/texturePacks/java/moe/nea/firmament/features/texturepack/FirmamentRootPredicateSerializer.kt
+++ b/src/texturePacks/java/moe/nea/firmament/features/texturepack/FirmamentRootPredicateSerializer.kt
@@ -6,6 +6,7 @@ import kotlinx.serialization.descriptors.SerialDescriptor
import kotlinx.serialization.encoding.Decoder
import kotlinx.serialization.encoding.Encoder
import moe.nea.firmament.features.texturepack.predicates.AndPredicate
+import moe.nea.firmament.util.json.intoGson
object FirmamentRootPredicateSerializer : KSerializer<FirmamentModelPredicate> {
val delegateSerializer = kotlinx.serialization.json.JsonObject.serializer()
diff --git a/src/texturePacks/java/moe/nea/firmament/features/texturepack/HeadModelChooser.kt b/src/texturePacks/java/moe/nea/firmament/features/texturepack/HeadModelChooser.kt
new file mode 100644
index 0000000..03496aa
--- /dev/null
+++ b/src/texturePacks/java/moe/nea/firmament/features/texturepack/HeadModelChooser.kt
@@ -0,0 +1,92 @@
+package moe.nea.firmament.features.texturepack
+
+import com.google.gson.JsonObject
+import com.mojang.serialization.MapCodec
+import com.mojang.serialization.codecs.RecordCodecBuilder
+import net.minecraft.client.renderer.item.ItemModelResolver
+import net.minecraft.client.renderer.item.ItemStackRenderState
+import net.minecraft.client.renderer.item.BlockModelWrapper
+import net.minecraft.client.renderer.item.ItemModel
+import net.minecraft.client.renderer.item.ItemModels
+import net.minecraft.client.resources.model.ResolvableModel
+import net.minecraft.client.multiplayer.ClientLevel
+import net.minecraft.world.entity.LivingEntity
+import net.minecraft.world.item.ItemDisplayContext
+import net.minecraft.world.item.ItemStack
+import net.minecraft.world.entity.ItemOwner
+import net.minecraft.resources.ResourceLocation
+
+object HeadModelChooser {
+ val IS_CHOOSING_HEAD_MODEL = ThreadLocal.withInitial { false }
+
+ interface HasExplicitHeadModelMarker {
+ fun markExplicitHead_Firmament()
+ fun isExplicitHeadModel_Firmament(): Boolean
+ companion object{
+ @JvmStatic
+ fun cast(state: ItemStackRenderState) = state as HasExplicitHeadModelMarker
+ }
+ }
+
+ data class Baked(val head: ItemModel, val regular: ItemModel) : ItemModel {
+
+ override fun update(
+ state: ItemStackRenderState,
+ stack: ItemStack?,
+ resolver: ItemModelResolver?,
+ displayContext: ItemDisplayContext,
+ world: ClientLevel?,
+ heldItemContext: ItemOwner?,
+ seed: Int
+ ) {
+ val instance =
+ if (IS_CHOOSING_HEAD_MODEL.get()) {
+ HasExplicitHeadModelMarker.cast(state).markExplicitHead_Firmament()
+ head
+ } else {
+ regular
+ }
+ instance.update(state, stack, resolver, displayContext, world, heldItemContext, seed)
+ }
+ }
+
+ data class Unbaked(
+ val head: ItemModel.Unbaked,
+ val regular: ItemModel.Unbaked,
+ ) : ItemModel.Unbaked {
+ override fun type(): MapCodec<out ItemModel.Unbaked> {
+ return CODEC
+ }
+
+ override fun bake(context: ItemModel.BakingContext): ItemModel {
+ return Baked(
+ head.bake(context),
+ regular.bake(context)
+ )
+ }
+
+ override fun resolveDependencies(resolver: ResolvableModel.Resolver) {
+ head.resolveDependencies(resolver)
+ regular.resolveDependencies(resolver)
+ }
+
+ companion object {
+ @JvmStatic
+ fun fromLegacyJson(jsonObject: JsonObject, unbakedModel: ItemModel.Unbaked): ItemModel.Unbaked {
+ val model = jsonObject["firmament:head_model"] ?: return unbakedModel
+ val modelUrl = model.asJsonPrimitive.asString
+ val headModel = BlockModelWrapper.Unbaked(ResourceLocation.parse(modelUrl), listOf())
+ return Unbaked(headModel, unbakedModel)
+ }
+
+ val CODEC = RecordCodecBuilder.mapCodec {
+ it.group(
+ ItemModels.CODEC.fieldOf("head")
+ .forGetter(Unbaked::head),
+ ItemModels.CODEC.fieldOf("regular")
+ .forGetter(Unbaked::regular),
+ ).apply(it, ::Unbaked)
+ }
+ }
+ }
+}
diff --git a/src/texturePacks/java/moe/nea/firmament/features/texturepack/PredicateModel.kt b/src/texturePacks/java/moe/nea/firmament/features/texturepack/PredicateModel.kt
index b52e96b..e53c9c7 100644
--- a/src/texturePacks/java/moe/nea/firmament/features/texturepack/PredicateModel.kt
+++ b/src/texturePacks/java/moe/nea/firmament/features/texturepack/PredicateModel.kt
@@ -4,51 +4,51 @@ import com.google.gson.JsonObject
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.BasicItemModel
-import net.minecraft.client.render.item.model.ItemModel
-import net.minecraft.client.render.item.model.ItemModelTypes
-import net.minecraft.client.render.item.tint.TintSource
-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
-import net.minecraft.util.Identifier
+import net.minecraft.client.renderer.item.ItemModelResolver
+import net.minecraft.client.renderer.item.ItemStackRenderState
+import net.minecraft.client.renderer.item.BlockModelWrapper
+import net.minecraft.client.renderer.item.ItemModel
+import net.minecraft.client.renderer.item.ItemModels
+import net.minecraft.client.resources.model.ResolvableModel
+import net.minecraft.client.multiplayer.ClientLevel
+import net.minecraft.world.entity.LivingEntity
+import net.minecraft.world.item.ItemDisplayContext
+import net.minecraft.world.item.ItemStack
+import net.minecraft.world.entity.ItemOwner
+import net.minecraft.resources.ResourceLocation
import moe.nea.firmament.features.texturepack.predicates.AndPredicate
class PredicateModel {
data class Baked(
- val fallback: ItemModel,
- val overrides: List<Override>
+ val fallback: ItemModel,
+ val overrides: List<Override>
) : ItemModel {
data class Override(
- val model: ItemModel,
- val predicate: FirmamentModelPredicate,
+ val model: ItemModel,
+ val predicate: FirmamentModelPredicate,
)
override fun update(
- state: ItemRenderState,
- stack: ItemStack,
- resolver: ItemModelManager,
- transformationMode: ModelTransformationMode,
- world: ClientWorld?,
- user: LivingEntity?,
- seed: Int
+ state: ItemStackRenderState?,
+ stack: ItemStack,
+ resolver: ItemModelResolver?,
+ displayContext: ItemDisplayContext?,
+ world: ClientLevel?,
+ heldItemContext: ItemOwner?,
+ seed: Int
) {
val model =
overrides
- .find { it.predicate.test(stack) }
+ .findLast { it.predicate.test(stack, heldItemContext?.asLivingEntity()) }
?.model
?: fallback
- model.update(state, stack, resolver, transformationMode, world, user, seed)
+ model.update(state, stack, resolver, displayContext, world, heldItemContext, seed)
}
}
data class Unbaked(
- val fallback: ItemModel.Unbaked,
- val overrides: List<Override>,
+ val fallback: ItemModel.Unbaked,
+ val overrides: List<Override>,
) : ItemModel.Unbaked {
companion object {
@JvmStatic
@@ -57,10 +57,10 @@ class PredicateModel {
val newOverrides = ArrayList<Override>()
for (legacyOverride in legacyOverrides) {
legacyOverride as JsonObject
- val overrideModel = Identifier.tryParse(legacyOverride.get("model")?.asString ?: continue) ?: continue
+ val overrideModel = ResourceLocation.tryParse(legacyOverride.get("model")?.asString ?: continue) ?: continue
val predicate = CustomModelOverrideParser.parsePredicates(legacyOverride.getAsJsonObject("predicate"))
newOverrides.add(Override(
- BasicItemModel.Unbaked(overrideModel, listOf()),
+ BlockModelWrapper.Unbaked(overrideModel, listOf()),
AndPredicate(predicate.toTypedArray())
))
}
@@ -69,34 +69,34 @@ class PredicateModel {
val OVERRIDE_CODEC: Codec<Override> = RecordCodecBuilder.create {
it.group(
- ItemModelTypes.CODEC.fieldOf("model").forGetter(Override::model),
+ ItemModels.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),
+ ItemModels.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,
+ val model: ItemModel.Unbaked,
+ val predicate: FirmamentModelPredicate,
)
- override fun resolve(resolver: ResolvableModel.Resolver) {
- fallback.resolve(resolver)
- overrides.forEach { it.model.resolve(resolver) }
+ override fun resolveDependencies(resolver: ResolvableModel.Resolver) {
+ fallback.resolveDependencies(resolver)
+ overrides.forEach { it.model.resolveDependencies(resolver) }
}
- override fun getCodec(): MapCodec<out Unbaked> {
+ override fun type(): MapCodec<out Unbaked> {
return CODEC
}
- override fun bake(context: ItemModel.BakeContext): ItemModel {
+ override fun bake(context: ItemModel.BakingContext): ItemModel {
return Baked(
fallback.bake(context),
overrides.map { Baked.Override(it.model.bake(context), it.predicate) }
diff --git a/src/texturePacks/java/moe/nea/firmament/features/texturepack/StringMatcher.kt b/src/texturePacks/java/moe/nea/firmament/features/texturepack/StringMatcher.kt
index 2b13284..52800bd 100644
--- a/src/texturePacks/java/moe/nea/firmament/features/texturepack/StringMatcher.kt
+++ b/src/texturePacks/java/moe/nea/firmament/features/texturepack/StringMatcher.kt
@@ -1,4 +1,3 @@
-
package moe.nea.firmament.features.texturepack
import com.google.gson.JsonArray
@@ -13,147 +12,137 @@ 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 net.minecraft.nbt.StringTag
+import net.minecraft.network.chat.Component
+import moe.nea.firmament.util.json.intoGson
+import moe.nea.firmament.util.json.intoKotlinJson
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
- }
- }
+ fun matches(string: String): Boolean
+ fun matches(text: Component): Boolean {
+ return matches(text.string)
+ }
+
+ val asRegex: java.util.regex.Pattern
+
+ fun matchWithGroups(string: String): MatchNamedGroupCollection?
+
+ fun matches(nbt: StringTag): Boolean {
+ val string = nbt.value
+ 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) {
+ // TODO: return matches(TextCodecs.CODEC.parse(MC.defaultRegistryNbtOps, string) ?: return false)
+ }
+ return matches(string)
+ }
+
+ class Equals(input: String, val stripColorCodes: Boolean) : StringMatcher {
+ override val asRegex by lazy(LazyThreadSafetyMode.PUBLICATION) { input.toPattern(java.util.regex.Pattern.LITERAL) }
+ 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 matchWithGroups(string: String): MatchNamedGroupCollection? {
+ if (matches(string))
+ return object : MatchNamedGroupCollection {
+ override fun get(name: String): MatchGroup? {
+ return null
+ }
+
+ override fun get(index: Int): MatchGroup? {
+ return null
+ }
+
+ override val size: Int
+ get() = 0
+
+ override fun isEmpty(): Boolean {
+ return true
+ }
+
+ override fun contains(element: MatchGroup?): Boolean {
+ return false
+ }
+
+ override fun iterator(): Iterator<MatchGroup?> {
+ return emptyList<MatchGroup>().iterator()
+ }
+
+ override fun containsAll(elements: Collection<MatchGroup?>): Boolean {
+ return elements.isEmpty()
+ }
+ }
+ return null
+ }
+
+ override fun toString(): String {
+ return "Equals($expected, stripColorCodes = $stripColorCodes)"
+ }
+ }
+
+ class Pattern(val patternWithColorCodes: String, val stripColorCodes: Boolean) : StringMatcher {
+ private val pattern = patternWithColorCodes.toRegex()
+ override val asRegex = pattern.toPattern()
+ override fun matches(string: String): Boolean {
+ return pattern.matches(if (stripColorCodes) string.removeColorCodes() else string)
+ }
+
+ override fun matchWithGroups(string: String): MatchNamedGroupCollection? {
+ return pattern.matchEntire(if (stripColorCodes) string.removeColorCodes() else string)?.groups as MatchNamedGroupCollection?
+ }
+
+ 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")
+ }
+ }
}
diff --git a/src/texturePacks/java/moe/nea/firmament/features/texturepack/TreeishTextReplacer.kt b/src/texturePacks/java/moe/nea/firmament/features/texturepack/TreeishTextReplacer.kt
new file mode 100644
index 0000000..ed486f5
--- /dev/null
+++ b/src/texturePacks/java/moe/nea/firmament/features/texturepack/TreeishTextReplacer.kt
@@ -0,0 +1,79 @@
+package moe.nea.firmament.features.texturepack
+
+import java.util.regex.Matcher
+import util.json.CodecSerializer
+import kotlinx.serialization.Serializable
+import net.minecraft.network.chat.Style
+import net.minecraft.network.chat.Component
+import net.minecraft.network.chat.ComponentSerialization
+import moe.nea.firmament.util.directLiteralStringContent
+import moe.nea.firmament.util.transformEachRecursively
+
+@Serializable
+data class TreeishTextReplacer(
+ val match: StringMatcher,
+ val replacements: List<SubPartReplacement>
+) {
+ @Serializable
+ data class SubPartReplacement(
+ val match: StringMatcher,
+ val style: @Serializable(StyleSerializer::class) Style? = null,
+ val replace: @Serializable(TextSerializer::class) Component,
+ )
+
+ object TextSerializer : CodecSerializer<Component>(ComponentSerialization.CODEC)
+ object StyleSerializer : CodecSerializer<Style>(Style.Serializer.CODEC)
+ companion object {
+ val pattern = "[$]\\{(?<name>[^}]+)}".toPattern()
+ fun injectMatchResults(text: Component, matches: Matcher): Component {
+ return text.transformEachRecursively { it ->
+ val content = it.directLiteralStringContent ?: return@transformEachRecursively it
+ val matcher = pattern.matcher(content)
+ val builder = StringBuilder()
+ while (matcher.find()) {
+ matcher.appendReplacement(builder, matches.group(matcher.group("name")).toString())
+ }
+ matcher.appendTail(builder)
+ Component.literal(builder.toString()).setStyle(it.style)
+ }
+ }
+ }
+
+ fun match(text: Component): Boolean {
+ return match.matches(text)
+ }
+
+ fun replaceText(text: Component): Component {
+ return text.transformEachRecursively { part ->
+ var part: Component = part
+ for (replacement in replacements) {
+ val rawPartText = part.string
+ replacement.style?.let { expectedStyle ->
+ val parentStyle = part.style
+ val parented = expectedStyle.applyTo(parentStyle)
+ if (parented.isStrikethrough != parentStyle.isStrikethrough
+ || parented.isObfuscated != parentStyle.isObfuscated
+ || parented.isBold != parentStyle.isBold
+ || parented.isUnderlined != parentStyle.isUnderlined
+ || parented.isItalic != parentStyle.isItalic
+ || parented.color?.value != parentStyle.color?.value)
+ continue
+ }
+ val matcher = replacement.match.asRegex.matcher(rawPartText)
+ if (!matcher.find()) continue
+ val p = Component.literal("")
+ p.setStyle(part.style)
+ var lastAppendPosition = 0
+ do {
+ p.append(rawPartText.substring(lastAppendPosition, matcher.start()))
+ lastAppendPosition = matcher.end()
+ p.append(injectMatchResults(replacement.replace, matcher))
+ } while (matcher.find())
+ p.append(rawPartText.substring(lastAppendPosition))
+ part = p
+ }
+ part
+ }
+ }
+
+}
diff --git a/src/texturePacks/java/moe/nea/firmament/features/texturepack/predicates/AlwaysPredicate.kt b/src/texturePacks/java/moe/nea/firmament/features/texturepack/predicates/AlwaysPredicate.kt
index 7e0ddb1..f32b673 100644
--- a/src/texturePacks/java/moe/nea/firmament/features/texturepack/predicates/AlwaysPredicate.kt
+++ b/src/texturePacks/java/moe/nea/firmament/features/texturepack/predicates/AlwaysPredicate.kt
@@ -4,7 +4,7 @@ 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
+import net.minecraft.world.item.ItemStack
object AlwaysPredicate : FirmamentModelPredicate {
override fun test(stack: ItemStack): Boolean {
diff --git a/src/texturePacks/java/moe/nea/firmament/features/texturepack/predicates/AndPredicate.kt b/src/texturePacks/java/moe/nea/firmament/features/texturepack/predicates/AndPredicate.kt
index 99abaaa..c2ed24f 100644
--- a/src/texturePacks/java/moe/nea/firmament/features/texturepack/predicates/AndPredicate.kt
+++ b/src/texturePacks/java/moe/nea/firmament/features/texturepack/predicates/AndPredicate.kt
@@ -3,15 +3,16 @@ package moe.nea.firmament.features.texturepack.predicates
import com.google.gson.JsonArray
import com.google.gson.JsonElement
import com.google.gson.JsonObject
+import net.minecraft.world.entity.LivingEntity
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
+import net.minecraft.world.item.ItemStack
class AndPredicate(val children: Array<FirmamentModelPredicate>) : FirmamentModelPredicate {
- override fun test(stack: ItemStack): Boolean {
- return children.all { it.test(stack) }
- }
+ override fun test(stack: ItemStack, holder: LivingEntity?): Boolean {
+ return children.all { it.test(stack, holder) }
+ }
object Parser : FirmamentModelPredicateParser {
override fun parse(jsonElement: JsonElement): FirmamentModelPredicate {
diff --git a/src/texturePacks/java/moe/nea/firmament/features/texturepack/predicates/CastPredicate.kt b/src/texturePacks/java/moe/nea/firmament/features/texturepack/predicates/CastPredicate.kt
index 7ccaadf..b54dd5d 100644
--- a/src/texturePacks/java/moe/nea/firmament/features/texturepack/predicates/CastPredicate.kt
+++ b/src/texturePacks/java/moe/nea/firmament/features/texturepack/predicates/CastPredicate.kt
@@ -1,10 +1,11 @@
package moe.nea.firmament.features.texturepack.predicates
import com.google.gson.JsonElement
-import net.minecraft.item.ItemStack
+import net.minecraft.world.entity.LivingEntity
+import net.minecraft.world.entity.player.Player
+import net.minecraft.world.item.ItemStack
import moe.nea.firmament.features.texturepack.FirmamentModelPredicate
import moe.nea.firmament.features.texturepack.FirmamentModelPredicateParser
-import moe.nea.firmament.util.MC
class CastPredicate : FirmamentModelPredicate {
object Parser : FirmamentModelPredicateParser {
@@ -14,7 +15,11 @@ class CastPredicate : FirmamentModelPredicate {
}
}
+ override fun test(stack: ItemStack, holder: LivingEntity?): Boolean {
+ return (holder as? Player)?.fishing != null && holder.mainHandItem === stack
+ }
+
override fun test(stack: ItemStack): Boolean {
- return MC.player?.fishHook != null // TODO pass through more of the model predicate context
+ return false
}
}
diff --git a/src/texturePacks/java/moe/nea/firmament/features/texturepack/predicates/DisplayNamePredicate.kt b/src/texturePacks/java/moe/nea/firmament/features/texturepack/predicates/DisplayNamePredicate.kt
index 04c7a2b..0d4fb84 100644
--- a/src/texturePacks/java/moe/nea/firmament/features/texturepack/predicates/DisplayNamePredicate.kt
+++ b/src/texturePacks/java/moe/nea/firmament/features/texturepack/predicates/DisplayNamePredicate.kt
@@ -5,7 +5,7 @@ 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 net.minecraft.world.item.ItemStack
import moe.nea.firmament.util.mc.displayNameAccordingToNbt
data class DisplayNamePredicate(val stringMatcher: StringMatcher) : FirmamentModelPredicate {
diff --git a/src/texturePacks/java/moe/nea/firmament/features/texturepack/predicates/ExtraAttributesPredicate.kt b/src/texturePacks/java/moe/nea/firmament/features/texturepack/predicates/ExtraAttributesPredicate.kt
index 3c8023d..e94f5c6 100644
--- a/src/texturePacks/java/moe/nea/firmament/features/texturepack/predicates/ExtraAttributesPredicate.kt
+++ b/src/texturePacks/java/moe/nea/firmament/features/texturepack/predicates/ExtraAttributesPredicate.kt
@@ -1,215 +1,220 @@
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 kotlin.jvm.optionals.getOrDefault
+import kotlin.jvm.optionals.getOrNull
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 net.minecraft.world.item.ItemStack
+import net.minecraft.nbt.ByteTag
+import net.minecraft.nbt.DoubleTag
+import net.minecraft.nbt.Tag
+import net.minecraft.nbt.FloatTag
+import net.minecraft.nbt.IntTag
+import net.minecraft.nbt.LongTag
+import net.minecraft.nbt.ShortTag
import moe.nea.firmament.util.extraAttributes
+import moe.nea.firmament.util.mc.NbtPrism
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(
+ fun matches(nbt: Tag): 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? IntTag)?.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? FloatTag)?.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? DoubleTag)?.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? LongTag)?.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? ShortTag)?.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? ByteTag)?.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 nbtExtractor: (Tag) -> 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)"
- }
- }
+ ): 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: Tag): Boolean {
+ return when (nbt) {
+ is ByteTag -> nbt.byteValue().toLong() == number
+ is IntTag -> nbt.intValue().toLong() == number
+ is ShortTag -> nbt.shortValue().toLong() == number
+ is LongTag -> nbt.longValue().toLong() == number
+ else -> false
+ }
+ }
+
+ }
+
+ class MatchStringExact(val string: String) : NbtMatcher {
+ override fun matches(nbt: Tag): Boolean {
+ return nbt.asString().getOrNull() == string
+ }
+
+ override fun toString(): String {
+ return "MatchNbtStringExactly($string)"
+ }
+ }
+
+ class MatchString(val string: StringMatcher) : NbtMatcher {
+ override fun matches(nbt: Tag): Boolean {
+ return nbt.asString().map(string::matches).getOrDefault(false)
+ }
+
+ override fun toString(): String {
+ return "MatchNbtString($string)"
+ }
+ }
}
data class ExtraAttributesPredicate(
@@ -217,55 +222,20 @@ data class ExtraAttributesPredicate(
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) }
- }
+ object Parser : FirmamentModelPredicateParser {
+ override fun parse(jsonElement: JsonElement): FirmamentModelPredicate? {
+ if (jsonElement !is JsonObject) return null
+ val path = jsonElement.get("path") ?: return null
+ val prism = NbtPrism.fromElement(path) ?: return null
+ val matcher = NbtMatcher.Parser.parse(jsonElement.get("match") ?: jsonElement)
+ ?: return null
+ return ExtraAttributesPredicate(prism, 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
- }
-}
diff --git a/src/texturePacks/java/moe/nea/firmament/features/texturepack/predicates/GenericComponentPredicate.kt b/src/texturePacks/java/moe/nea/firmament/features/texturepack/predicates/GenericComponentPredicate.kt
new file mode 100644
index 0000000..4b0b7cb
--- /dev/null
+++ b/src/texturePacks/java/moe/nea/firmament/features/texturepack/predicates/GenericComponentPredicate.kt
@@ -0,0 +1,59 @@
+package moe.nea.firmament.features.texturepack.predicates
+
+import com.google.gson.JsonElement
+import com.google.gson.JsonObject
+import com.mojang.serialization.Codec
+import kotlin.jvm.optionals.getOrNull
+import net.minecraft.core.component.DataComponentType
+import net.minecraft.world.item.component.CustomData
+import net.minecraft.world.entity.LivingEntity
+import net.minecraft.world.item.ItemStack
+import net.minecraft.nbt.NbtOps
+import net.minecraft.resources.ResourceKey
+import net.minecraft.core.registries.Registries
+import net.minecraft.resources.ResourceLocation
+import moe.nea.firmament.features.texturepack.FirmamentModelPredicate
+import moe.nea.firmament.features.texturepack.FirmamentModelPredicateParser
+import moe.nea.firmament.util.MC
+import moe.nea.firmament.util.mc.NbtPrism
+import moe.nea.firmament.util.mc.unsafeNbt
+
+data class GenericComponentPredicate<T>(
+ val componentType: DataComponentType<T>,
+ val codec: Codec<T>,
+ val path: NbtPrism,
+ val matcher: NbtMatcher,
+) : FirmamentModelPredicate {
+ constructor(componentType: DataComponentType<T>, path: NbtPrism, matcher: NbtMatcher)
+ : this(componentType, componentType.codecOrThrow(), path, matcher)
+
+ override fun test(stack: ItemStack, holder: LivingEntity?): Boolean {
+ val component = stack.get(componentType) ?: return false
+ // TODO: cache this
+ val nbt =
+ if (component is CustomData) component.unsafeNbt
+ else codec.encodeStart(NbtOps.INSTANCE, component)
+ .resultOrPartial().getOrNull() ?: return false
+ return path.access(nbt).any { matcher.matches(it) }
+ }
+
+ object Parser : FirmamentModelPredicateParser {
+ override fun parse(jsonElement: JsonElement): GenericComponentPredicate<*>? {
+ if (jsonElement !is JsonObject) return null
+ val path = jsonElement.get("path") ?: return null
+ val prism = NbtPrism.fromElement(path) ?: return null
+ val matcher = NbtMatcher.Parser.parse(jsonElement.get("match") ?: jsonElement)
+ ?: return null
+ val component = MC.currentOrDefaultRegistries
+ .lookupOrThrow(Registries.DATA_COMPONENT_TYPE)
+ .getOrThrow(
+ ResourceKey.create(
+ Registries.DATA_COMPONENT_TYPE,
+ ResourceLocation.parse(jsonElement.get("component").asString)
+ )
+ ).value()
+ return GenericComponentPredicate(component, prism, matcher)
+ }
+ }
+
+}
diff --git a/src/texturePacks/java/moe/nea/firmament/features/texturepack/predicates/ItemPredicate.kt b/src/texturePacks/java/moe/nea/firmament/features/texturepack/predicates/ItemPredicate.kt
index 3cb80c7..e90d9fb 100644
--- a/src/texturePacks/java/moe/nea/firmament/features/texturepack/predicates/ItemPredicate.kt
+++ b/src/texturePacks/java/moe/nea/firmament/features/texturepack/predicates/ItemPredicate.kt
@@ -6,27 +6,27 @@ 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 net.minecraft.world.item.Item
+import net.minecraft.world.item.ItemStack
+import net.minecraft.resources.ResourceKey
+import net.minecraft.core.registries.Registries
+import net.minecraft.resources.ResourceLocation
import moe.nea.firmament.util.MC
class ItemPredicate(
val item: Item
) : FirmamentModelPredicate {
override fun test(stack: ItemStack): Boolean {
- return stack.item == item
+ return stack.`is`(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)
+ val itemKey = ResourceKey.create(Registries.ITEM,
+ ResourceLocation.tryParse(jsonElement.asString)
?: return null)
- return ItemPredicate(MC.defaultItems.getOptional(itemKey).getOrNull()?.value() ?: return null)
+ return ItemPredicate(MC.defaultItems.get(itemKey).getOrNull()?.value() ?: return null)
}
return null
}
diff --git a/src/texturePacks/java/moe/nea/firmament/features/texturepack/predicates/LorePredicate.kt b/src/texturePacks/java/moe/nea/firmament/features/texturepack/predicates/LorePredicate.kt
index f0b4737..6b2a8b9 100644
--- a/src/texturePacks/java/moe/nea/firmament/features/texturepack/predicates/LorePredicate.kt
+++ b/src/texturePacks/java/moe/nea/firmament/features/texturepack/predicates/LorePredicate.kt
@@ -5,7 +5,7 @@ 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 net.minecraft.world.item.ItemStack
import moe.nea.firmament.util.mc.loreAccordingToNbt
class LorePredicate(val matcher: StringMatcher) : FirmamentModelPredicate {
diff --git a/src/texturePacks/java/moe/nea/firmament/features/texturepack/predicates/NotPredicate.kt b/src/texturePacks/java/moe/nea/firmament/features/texturepack/predicates/NotPredicate.kt
index 4986ad9..d0ad11c 100644
--- a/src/texturePacks/java/moe/nea/firmament/features/texturepack/predicates/NotPredicate.kt
+++ b/src/texturePacks/java/moe/nea/firmament/features/texturepack/predicates/NotPredicate.kt
@@ -6,7 +6,7 @@ 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
+import net.minecraft.world.item.ItemStack
class NotPredicate(val children: Array<FirmamentModelPredicate>) : FirmamentModelPredicate {
override fun test(stack: ItemStack): Boolean {
diff --git a/src/texturePacks/java/moe/nea/firmament/features/texturepack/predicates/OrPredicate.kt b/src/texturePacks/java/moe/nea/firmament/features/texturepack/predicates/OrPredicate.kt
index e3093cd..c32543f 100644
--- a/src/texturePacks/java/moe/nea/firmament/features/texturepack/predicates/OrPredicate.kt
+++ b/src/texturePacks/java/moe/nea/firmament/features/texturepack/predicates/OrPredicate.kt
@@ -7,7 +7,7 @@ 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
+import net.minecraft.world.item.ItemStack
class OrPredicate(val children: Array<FirmamentModelPredicate>) : FirmamentModelPredicate {
override fun test(stack: ItemStack): Boolean {
diff --git a/src/texturePacks/java/moe/nea/firmament/features/texturepack/predicates/PetPredicate.kt b/src/texturePacks/java/moe/nea/firmament/features/texturepack/predicates/PetPredicate.kt
index b30b7c9..9de51d2 100644
--- a/src/texturePacks/java/moe/nea/firmament/features/texturepack/predicates/PetPredicate.kt
+++ b/src/texturePacks/java/moe/nea/firmament/features/texturepack/predicates/PetPredicate.kt
@@ -7,7 +7,7 @@ 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 net.minecraft.world.item.ItemStack
import moe.nea.firmament.repo.ExpLadders
import moe.nea.firmament.util.petData
diff --git a/src/texturePacks/java/moe/nea/firmament/features/texturepack/predicates/PullingPredicate.kt b/src/texturePacks/java/moe/nea/firmament/features/texturepack/predicates/PullingPredicate.kt
new file mode 100644
index 0000000..330fe88
--- /dev/null
+++ b/src/texturePacks/java/moe/nea/firmament/features/texturepack/predicates/PullingPredicate.kt
@@ -0,0 +1,26 @@
+package moe.nea.firmament.features.texturepack.predicates
+
+import com.google.gson.JsonElement
+import net.minecraft.world.entity.LivingEntity
+import net.minecraft.world.item.BowItem
+import net.minecraft.world.item.ItemStack
+import moe.nea.firmament.features.texturepack.FirmamentModelPredicate
+import moe.nea.firmament.features.texturepack.FirmamentModelPredicateParser
+
+class PullingPredicate(val percentage: Double) : FirmamentModelPredicate {
+ companion object {
+ val AnyPulling = PullingPredicate(0.1)
+ }
+
+ object Parser : FirmamentModelPredicateParser {
+ override fun parse(jsonElement: JsonElement): FirmamentModelPredicate? {
+ return PullingPredicate(jsonElement.asDouble)
+ }
+ }
+
+ override fun test(stack: ItemStack, holder: LivingEntity?): Boolean {
+ if (holder == null) return false
+ return BowItem.getPowerForTime(holder.ticksUsingItem) >= percentage
+ }
+
+}
diff --git a/src/texturePacks/java/moe/nea/firmament/features/texturepack/predicates/SkullPredicate.kt b/src/texturePacks/java/moe/nea/firmament/features/texturepack/predicates/SkullPredicate.kt
new file mode 100644
index 0000000..cbef02e
--- /dev/null
+++ b/src/texturePacks/java/moe/nea/firmament/features/texturepack/predicates/SkullPredicate.kt
@@ -0,0 +1,63 @@
+package moe.nea.firmament.features.texturepack.predicates
+
+import com.google.gson.JsonElement
+import com.mojang.authlib.minecraft.MinecraftProfileTexture
+import java.util.UUID
+import kotlin.jvm.optionals.getOrNull
+import net.minecraft.core.component.DataComponents
+import net.minecraft.world.entity.LivingEntity
+import net.minecraft.world.item.ItemStack
+import net.minecraft.world.item.Items
+import moe.nea.firmament.features.texturepack.FirmamentModelPredicate
+import moe.nea.firmament.features.texturepack.FirmamentModelPredicateParser
+import moe.nea.firmament.features.texturepack.StringMatcher
+import moe.nea.firmament.util.mc.decodeProfileTextureProperty
+import moe.nea.firmament.util.parsePotentiallyDashlessUUID
+
+class SkullPredicate(
+ val profileId: UUID?,
+ val textureProfileId: UUID?,
+ val skinUrl: StringMatcher?,
+ val textureValue: StringMatcher?,
+) : FirmamentModelPredicate {
+ object Parser : FirmamentModelPredicateParser {
+ override fun parse(jsonElement: JsonElement): FirmamentModelPredicate? {
+ val obj = jsonElement.asJsonObject
+ val profileId = obj.getAsJsonPrimitive("profileId")
+ ?.asString?.let(::parsePotentiallyDashlessUUID)
+ val textureProfileId = obj.getAsJsonPrimitive("textureProfileId")
+ ?.asString?.let(::parsePotentiallyDashlessUUID)
+ val textureValue = obj.get("textureValue")?.let(StringMatcher::parse)
+ val skinUrl = obj.get("skinUrl")?.let(StringMatcher::parse)
+ return SkullPredicate(profileId, textureProfileId, skinUrl, textureValue)
+ }
+ }
+
+ override fun test(stack: ItemStack, holder: LivingEntity?): Boolean {
+ if (!stack.`is`(Items.PLAYER_HEAD)) return false
+ val profile = stack.get(DataComponents.PROFILE) ?: return false
+ val textureProperty = profile.partialProfile().properties["textures"].firstOrNull()
+ val textureMode = lazy(LazyThreadSafetyMode.NONE) {
+ decodeProfileTextureProperty(textureProperty ?: return@lazy null)
+ }
+ when {
+ profileId != null
+ && profileId != profile.partialProfile().id ->
+ return false
+
+ textureValue != null
+ && !textureValue.matches(textureProperty?.value ?: "") ->
+ return false
+
+ skinUrl != null
+ && !skinUrl.matches(textureMode.value?.textures?.get(MinecraftProfileTexture.Type.SKIN)?.url ?: "") ->
+ return false
+
+ textureProfileId != null
+ && textureProfileId != textureMode.value?.profileId ->
+ return false
+
+ else -> return true
+ }
+ }
+}
diff --git a/src/texturePacks/java/moe/nea/firmament/mixins/custommodels/ApplyHeadModelInItemRenderer.java b/src/texturePacks/java/moe/nea/firmament/mixins/custommodels/ApplyHeadModelInItemRenderer.java
index 4665829..b998251 100644
--- a/src/texturePacks/java/moe/nea/firmament/mixins/custommodels/ApplyHeadModelInItemRenderer.java
+++ b/src/texturePacks/java/moe/nea/firmament/mixins/custommodels/ApplyHeadModelInItemRenderer.java
@@ -1,11 +1,11 @@
package moe.nea.firmament.mixins.custommodels;
-import net.minecraft.client.render.entity.LivingEntityRenderer;
-import net.minecraft.client.render.entity.model.EntityModel;
-import net.minecraft.client.render.entity.state.LivingEntityRenderState;
-import net.minecraft.entity.LivingEntity;
-import net.minecraft.entity.decoration.DisplayEntity;
+import net.minecraft.client.renderer.entity.LivingEntityRenderer;
+import net.minecraft.client.model.EntityModel;
+import net.minecraft.client.renderer.entity.state.LivingEntityRenderState;
+import net.minecraft.world.entity.LivingEntity;
+import net.minecraft.world.entity.Display;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
@@ -15,7 +15,7 @@ import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
public class ApplyHeadModelInItemRenderer<T extends LivingEntity, S extends LivingEntityRenderState, M extends EntityModel<? super S>> {
// TODO: replace head_model with a condition model (if possible, automatically)
// TODO: ItemAsset.CODEC should upgrade partials
- @Inject(method = "updateRenderState(Lnet/minecraft/entity/LivingEntity;Lnet/minecraft/client/render/entity/state/LivingEntityRenderState;F)V",
+ @Inject(method = "extractRenderState(Lnet/minecraft/world/entity/LivingEntity;Lnet/minecraft/client/renderer/entity/state/LivingEntityRenderState;F)V",
at = @At("TAIL"))
private void updateHeadState(T livingEntity, S livingEntityRenderState, float f, CallbackInfo ci) {
diff --git a/src/texturePacks/java/moe/nea/firmament/mixins/custommodels/BuildExtraBlockStateModels.java b/src/texturePacks/java/moe/nea/firmament/mixins/custommodels/BuildExtraBlockStateModels.java
new file mode 100644
index 0000000..2e22eaa
--- /dev/null
+++ b/src/texturePacks/java/moe/nea/firmament/mixins/custommodels/BuildExtraBlockStateModels.java
@@ -0,0 +1,24 @@
+package moe.nea.firmament.mixins.custommodels;
+
+import com.llamalad7.mixinextras.injector.ModifyReturnValue;
+import com.llamalad7.mixinextras.sugar.Local;
+import moe.nea.firmament.features.texturepack.CustomBlockTextures;
+import net.minecraft.client.resources.model.ModelBaker;
+import net.minecraft.client.resources.model.ModelBakery;
+import org.spongepowered.asm.mixin.Mixin;
+import org.spongepowered.asm.mixin.injection.At;
+
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.Executor;
+
+@Mixin(ModelBakery.class)
+public class BuildExtraBlockStateModels {
+ @ModifyReturnValue(method = "bakeModels", at = @At("RETURN"))
+ private CompletableFuture<ModelBakery.BakingResult> injectMoreBlockModels(CompletableFuture<ModelBakery.BakingResult> original, @Local ModelBakery.ModelBakerImpl baker, @Local(argsOnly = true) Executor executor) {
+ ModelBaker b = baker;
+ return original.thenCombine(
+ CustomBlockTextures.createBakedModels(b, executor),
+ (a, _void) -> a
+ );
+ }
+}
diff --git a/src/texturePacks/java/moe/nea/firmament/mixins/custommodels/CustomSkullTexturePatch.java b/src/texturePacks/java/moe/nea/firmament/mixins/custommodels/CustomSkullTexturePatch.java
index fede766..3a6e83a 100644
--- a/src/texturePacks/java/moe/nea/firmament/mixins/custommodels/CustomSkullTexturePatch.java
+++ b/src/texturePacks/java/moe/nea/firmament/mixins/custommodels/CustomSkullTexturePatch.java
@@ -3,24 +3,25 @@
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 net.minecraft.world.level.block.SkullBlock;
+import net.minecraft.world.level.block.entity.SkullBlockEntity;
+import net.minecraft.client.renderer.RenderType;
+import net.minecraft.client.renderer.blockentity.SkullBlockRenderer;
+import net.minecraft.world.item.component.ResolvableProfile;
+import net.minecraft.resources.ResourceLocation;
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)
+@Mixin(SkullBlockRenderer.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;",
+ method = "resolveSkullRenderType",
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);
+ private void onGetRenderLayer(SkullBlock.Type skullType, SkullBlockEntity blockEntity, CallbackInfoReturnable<RenderType> cir) {
+ CustomSkyBlockTextures.INSTANCE.modifySkullTexture(skullType, blockEntity.getOwnerProfile(), cir);
}
}
diff --git a/src/texturePacks/java/moe/nea/firmament/mixins/custommodels/InsertExtraBlockModelDependencies.java b/src/texturePacks/java/moe/nea/firmament/mixins/custommodels/InsertExtraBlockModelDependencies.java
new file mode 100644
index 0000000..0ea2c46
--- /dev/null
+++ b/src/texturePacks/java/moe/nea/firmament/mixins/custommodels/InsertExtraBlockModelDependencies.java
@@ -0,0 +1,28 @@
+package moe.nea.firmament.mixins.custommodels;
+
+import com.llamalad7.mixinextras.sugar.Local;
+import moe.nea.firmament.features.texturepack.CustomBlockTextures;
+import net.minecraft.client.resources.model.ClientItemInfoLoader;
+import net.minecraft.client.resources.model.ModelManager;
+import net.minecraft.client.resources.model.BlockStateModelLoader;
+import net.minecraft.client.resources.model.ModelDiscovery;
+import net.minecraft.client.resources.model.UnbakedModel;
+import net.minecraft.resources.ResourceLocation;
+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;
+
+import java.util.Map;
+
+@Mixin(ModelManager.class)
+public class InsertExtraBlockModelDependencies {
+ @Inject(method = "discoverModelDependencies", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/resources/model/ModelDiscovery;addSpecialModel(Lnet/minecraft/resources/ResourceLocation;Lnet/minecraft/client/resources/model/UnbakedModel;)V", shift = At.Shift.AFTER))
+ private static void insertExtraModels(
+ Map<ResourceLocation, UnbakedModel> modelMap,
+ BlockStateModelLoader.LoadedModels stateDefinition,
+ ClientItemInfoLoader.LoadedClientInfos result,
+ CallbackInfoReturnable cir, @Local ModelDiscovery modelsCollector) {
+ CustomBlockTextures.collectExtraModels(modelsCollector);
+ }
+}
diff --git a/src/texturePacks/java/moe/nea/firmament/mixins/custommodels/ItemRenderStateExtraInfo.java b/src/texturePacks/java/moe/nea/firmament/mixins/custommodels/ItemRenderStateExtraInfo.java
new file mode 100644
index 0000000..2bf89ad
--- /dev/null
+++ b/src/texturePacks/java/moe/nea/firmament/mixins/custommodels/ItemRenderStateExtraInfo.java
@@ -0,0 +1,28 @@
+package moe.nea.firmament.mixins.custommodels;
+
+import moe.nea.firmament.features.texturepack.HeadModelChooser;
+import net.minecraft.client.renderer.item.ItemStackRenderState;
+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.CallbackInfo;
+
+@Mixin(ItemStackRenderState.class)
+public class ItemRenderStateExtraInfo implements HeadModelChooser.HasExplicitHeadModelMarker {
+ boolean hasExplicitHead_firmament = false;
+
+ @Inject(method = "clear", at = @At("HEAD"))
+ private void clear(CallbackInfo ci) {
+ hasExplicitHead_firmament = false;
+ }
+
+ @Override
+ public void markExplicitHead_Firmament() {
+ hasExplicitHead_firmament = true;
+ }
+
+ @Override
+ public boolean isExplicitHeadModel_Firmament() {
+ return hasExplicitHead_firmament;
+ }
+}
diff --git a/src/texturePacks/java/moe/nea/firmament/mixins/custommodels/LoadExtraBlockStates.java b/src/texturePacks/java/moe/nea/firmament/mixins/custommodels/LoadExtraBlockStates.java
new file mode 100644
index 0000000..5c5cf3e
--- /dev/null
+++ b/src/texturePacks/java/moe/nea/firmament/mixins/custommodels/LoadExtraBlockStates.java
@@ -0,0 +1,34 @@
+package moe.nea.firmament.mixins.custommodels;
+
+import com.llamalad7.mixinextras.injector.ModifyExpressionValue;
+import com.llamalad7.mixinextras.sugar.Local;
+import moe.nea.firmament.features.texturepack.CustomBlockTextures;
+import net.minecraft.world.level.block.Block;
+import net.minecraft.world.level.block.state.BlockState;
+import net.minecraft.client.resources.model.BlockStateModelLoader;
+import net.minecraft.server.packs.resources.Resource;
+import net.minecraft.world.level.block.state.StateDefinition;
+import net.minecraft.resources.ResourceLocation;
+import org.spongepowered.asm.mixin.Mixin;
+import org.spongepowered.asm.mixin.injection.At;
+
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.Executor;
+import java.util.function.Function;
+
+@Mixin(BlockStateModelLoader.class)
+public class LoadExtraBlockStates {
+ @ModifyExpressionValue(method = "loadBlockStates", at = @At(value = "INVOKE", target = "Ljava/util/concurrent/CompletableFuture;supplyAsync(Ljava/util/function/Supplier;Ljava/util/concurrent/Executor;)Ljava/util/concurrent/CompletableFuture;"))
+ private static CompletableFuture<Map<ResourceLocation, List<Resource>>> loadExtraModels(
+ CompletableFuture<Map<ResourceLocation, List<Resource>>> x,
+ @Local(argsOnly = true) Executor executor,
+ @Local Function<ResourceLocation, StateDefinition<Block, BlockState>> stateManagers
+ ) {
+ return x.thenCombineAsync(CustomBlockTextures.getPreparationFuture(), (original, extra) -> {
+ CustomBlockTextures.collectExtraBlockStateMaps(extra, original, stateManagers);
+ return original;
+ }, executor);
+ }
+}
diff --git a/src/texturePacks/java/moe/nea/firmament/mixins/custommodels/PatchArmorTexture.java b/src/texturePacks/java/moe/nea/firmament/mixins/custommodels/PatchArmorTexture.java
index 669da63..392ef54 100644
--- a/src/texturePacks/java/moe/nea/firmament/mixins/custommodels/PatchArmorTexture.java
+++ b/src/texturePacks/java/moe/nea/firmament/mixins/custommodels/PatchArmorTexture.java
@@ -6,25 +6,25 @@ 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 net.minecraft.client.renderer.entity.layers.HumanoidArmorLayer;
+import net.minecraft.core.component.DataComponentType;
+import net.minecraft.world.item.equipment.Equippable;
+import net.minecraft.world.entity.EquipmentSlot;
+import net.minecraft.world.item.ItemStack;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.injection.At;
-@Mixin(ArmorFeatureRenderer.class)
+@Mixin(HumanoidArmorLayer.class)
public class PatchArmorTexture {
@ModifyExpressionValue(
- method = "renderArmor",
+ method = "renderArmorPiece",
at = @At(
value = "INVOKE",
- target = "Lnet/minecraft/item/ItemStack;get(Lnet/minecraft/component/ComponentType;)Ljava/lang/Object;"))
+ target = "Lnet/minecraft/world/item/ItemStack;get(Lnet/minecraft/core/component/DataComponentType;)Ljava/lang/Object;"))
private Object overrideLayers(
- Object original, @Local(argsOnly = true) ItemStack itemStack, @Local(argsOnly = true) EquipmentSlot slot
+ Object original, @Local(argsOnly = true) ItemStack itemStack, @Local(argsOnly = true) EquipmentSlot slot
) {
var overrides = CustomGlobalArmorOverrides.overrideArmor(itemStack, slot);
- return overrides.orElse((EquippableComponent) original);
+ return overrides.orElse((Equippable) original);
}
}
diff --git a/src/texturePacks/java/moe/nea/firmament/mixins/custommodels/PatchLegacyArmorLayerSupport.java b/src/texturePacks/java/moe/nea/firmament/mixins/custommodels/PatchLegacyArmorLayerSupport.java
index 81ea6cd..5906892 100644
--- a/src/texturePacks/java/moe/nea/firmament/mixins/custommodels/PatchLegacyArmorLayerSupport.java
+++ b/src/texturePacks/java/moe/nea/firmament/mixins/custommodels/PatchLegacyArmorLayerSupport.java
@@ -1,23 +1,22 @@
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 net.minecraft.client.resources.model.EquipmentClientInfo;
+import net.minecraft.client.resources.model.EquipmentAssetManager;
+import net.minecraft.world.item.equipment.EquipmentAsset;
+import net.minecraft.resources.ResourceKey;
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;
// TODO: auto import legacy models, maybe!!! in a later patch tho
-@Mixin(EquipmentRenderer.class)
+@Mixin(EquipmentAssetManager.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);
+ @Inject(method = "get", at = @At(value = "HEAD"), cancellable = true)
+ private void patchModelLayers(ResourceKey<EquipmentAsset> assetKey, CallbackInfoReturnable<EquipmentClientInfo> cir) {
+ var modelOverride = CustomGlobalArmorOverrides.overrideArmorLayer(assetKey.location());
+ if (modelOverride != null)
+ cir.setReturnValue(modelOverride);
}
}
diff --git a/src/texturePacks/java/moe/nea/firmament/mixins/custommodels/PatchLegacyTexturePathsIntoArmorLayers.java b/src/texturePacks/java/moe/nea/firmament/mixins/custommodels/PatchLegacyTexturePathsIntoArmorLayers.java
new file mode 100644
index 0000000..f15a17c
--- /dev/null
+++ b/src/texturePacks/java/moe/nea/firmament/mixins/custommodels/PatchLegacyTexturePathsIntoArmorLayers.java
@@ -0,0 +1,37 @@
+package moe.nea.firmament.mixins.custommodels;
+
+
+import moe.nea.firmament.features.texturepack.CustomSkyBlockTextures;
+import moe.nea.firmament.util.MC;
+import net.minecraft.client.resources.model.EquipmentClientInfo;
+import net.minecraft.resources.ResourceLocation;
+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.CallbackInfoReturnable;
+
+@Mixin(EquipmentClientInfo.Layer.class)
+public class PatchLegacyTexturePathsIntoArmorLayers {
+ @Shadow
+ @Final
+ private ResourceLocation textureId;
+
+ @Inject(method = "getTextureLocation", at = @At("HEAD"), cancellable = true)
+ private void replaceWith1201TextureIfExists(EquipmentClientInfo.LayerType layerType, CallbackInfoReturnable<ResourceLocation> cir) {
+ if (!CustomSkyBlockTextures.TConfig.INSTANCE.getEnableLegacyMinecraftCompat())
+ return;
+ var resourceManager = MC.INSTANCE.getResourceManager();
+ // legacy format: "assets/{identifier.namespace}/textures/models/armor/{identifier.path}_layer_{isLegs ? 2 : 1}{suffix}.png"
+ // suffix is sadly not available to us here. this means leather armor will look a bit shite
+ var legacyIdentifier = this.textureId.withPath((textureName) -> {
+ return "textures/models/armor/" + textureName + "_layer_" +
+ (layerType == EquipmentClientInfo.LayerType.HUMANOID_LEGGINGS ? 2 : 1)
+ + ".png";
+ });
+ if (resourceManager.getResource(legacyIdentifier).isPresent()) {
+ cir.setReturnValue(legacyIdentifier);
+ }
+ }
+}
diff --git a/src/texturePacks/java/moe/nea/firmament/mixins/custommodels/ReplaceBlockBreakSoundPatch.java b/src/texturePacks/java/moe/nea/firmament/mixins/custommodels/ReplaceBlockBreakSoundPatch.java
index 9401889..39e23af 100644
--- a/src/texturePacks/java/moe/nea/firmament/mixins/custommodels/ReplaceBlockBreakSoundPatch.java
+++ b/src/texturePacks/java/moe/nea/firmament/mixins/custommodels/ReplaceBlockBreakSoundPatch.java
@@ -4,15 +4,15 @@ 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 net.minecraft.world.level.block.state.BlockState;
+import net.minecraft.client.renderer.LevelRenderer;
+import net.minecraft.world.level.block.SoundType;
+import net.minecraft.sounds.SoundEvent;
+import net.minecraft.core.BlockPos;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.injection.At;
-@Mixin(WorldRenderer.class)
+@Mixin(LevelRenderer.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;"))
diff --git a/src/texturePacks/java/moe/nea/firmament/mixins/custommodels/ReplaceBlockHitSoundPatch.java b/src/texturePacks/java/moe/nea/firmament/mixins/custommodels/ReplaceBlockHitSoundPatch.java
index f9a1d0d..7718735 100644
--- a/src/texturePacks/java/moe/nea/firmament/mixins/custommodels/ReplaceBlockHitSoundPatch.java
+++ b/src/texturePacks/java/moe/nea/firmament/mixins/custommodels/ReplaceBlockHitSoundPatch.java
@@ -4,26 +4,27 @@ 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 net.minecraft.world.level.block.state.BlockState;
+import net.minecraft.client.multiplayer.MultiPlayerGameMode;
+import net.minecraft.client.resources.sounds.SimpleSoundInstance;
+import net.minecraft.sounds.SoundSource;
+import net.minecraft.sounds.SoundEvent;
+import net.minecraft.core.BlockPos;
+import net.minecraft.util.RandomSource;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.injection.At;
-@Mixin(ClientPlayerInteractionManager.class)
+@Mixin(MultiPlayerGameMode.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) {
+ @WrapOperation(method = "continueDestroyBlock",
+ at = @At(value = "NEW", target = "(Lnet/minecraft/sounds/SoundEvent;Lnet/minecraft/sounds/SoundSource;FFLnet/minecraft/util/RandomSource;Lnet/minecraft/core/BlockPos;)Lnet/minecraft/client/resources/sounds/SimpleSoundInstance;"))
+ private SimpleSoundInstance replaceSound(
+ SoundEvent sound, SoundSource category, float volume, float pitch,
+ RandomSource random, BlockPos pos, Operation<SimpleSoundInstance> original,
+ @Local BlockState blockState) {
var replacement = CustomBlockTextures.getReplacement(blockState, pos);
if (replacement != null && replacement.getSound() != null) {
- sound = SoundEvent.of(replacement.getSound());
+ sound = SoundEvent.createVariableRangeEvent(replacement.getSound());
}
return original.call(sound, category, volume, pitch, random, pos);
}
diff --git a/src/texturePacks/java/moe/nea/firmament/mixins/custommodels/ReplaceBlockRenderManagerBlockModel.java b/src/texturePacks/java/moe/nea/firmament/mixins/custommodels/ReplaceBlockRenderManagerBlockModel.java
index 711b2af..c5f40b0 100644
--- a/src/texturePacks/java/moe/nea/firmament/mixins/custommodels/ReplaceBlockRenderManagerBlockModel.java
+++ b/src/texturePacks/java/moe/nea/firmament/mixins/custommodels/ReplaceBlockRenderManagerBlockModel.java
@@ -4,35 +4,34 @@ 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 net.minecraft.world.level.block.state.BlockState;
+import net.minecraft.client.renderer.block.BlockRenderDispatcher;
+import net.minecraft.client.renderer.chunk.SectionCompiler;
+import net.minecraft.client.renderer.block.model.BlockStateModel;
+import net.minecraft.core.BlockPos;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.injection.At;
-@Mixin(BlockRenderManager.class)
+@Mixin(SectionCompiler.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;
- }
+ @WrapOperation(method = "compile", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/renderer/block/BlockRenderDispatcher;getBlockModel(Lnet/minecraft/world/level/block/state/BlockState;)Lnet/minecraft/client/renderer/block/model/BlockStateModel;"))
+ private BlockStateModel replaceModelInRenderBlock(BlockRenderDispatcher instance, BlockState state, Operation<BlockStateModel> original, @Local(ordinal = 2) 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;
+ }
+//TODO: cover renderDamage model
+// @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;
+// }
}
diff --git a/src/texturePacks/java/moe/nea/firmament/mixins/custommodels/ReplaceFallbackBlockModel.java b/src/texturePacks/java/moe/nea/firmament/mixins/custommodels/ReplaceFallbackBlockModel.java
index 53ab74a..f2c2cb8 100644
--- a/src/texturePacks/java/moe/nea/firmament/mixins/custommodels/ReplaceFallbackBlockModel.java
+++ b/src/texturePacks/java/moe/nea/firmament/mixins/custommodels/ReplaceFallbackBlockModel.java
@@ -1,19 +1,19 @@
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 net.minecraft.world.level.block.state.BlockState;
+import net.minecraft.client.renderer.block.BlockModelShaper;
+import net.minecraft.client.renderer.block.model.BlockStateModel;
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)
+@Mixin(BlockModelShaper.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) {
+ @Inject(method = "getBlockModel", at = @At("HEAD"), cancellable = true)
+ private void getModel(BlockState state, CallbackInfoReturnable<BlockStateModel> cir) {
var replacement = CustomBlockTextures.getReplacementModel(state, null);
if (replacement != null)
cir.setReturnValue(replacement);
diff --git a/src/texturePacks/java/moe/nea/firmament/mixins/custommodels/ReplaceHeadModel.java b/src/texturePacks/java/moe/nea/firmament/mixins/custommodels/ReplaceHeadModel.java
new file mode 100644
index 0000000..1b9bc81
--- /dev/null
+++ b/src/texturePacks/java/moe/nea/firmament/mixins/custommodels/ReplaceHeadModel.java
@@ -0,0 +1,51 @@
+package moe.nea.firmament.mixins.custommodels;
+
+import moe.nea.firmament.features.texturepack.HeadModelChooser;
+import net.minecraft.client.renderer.item.ItemModelResolver;
+import net.minecraft.client.renderer.entity.LivingEntityRenderer;
+import net.minecraft.client.model.EntityModel;
+import net.minecraft.client.renderer.entity.state.LivingEntityRenderState;
+import net.minecraft.client.renderer.item.ItemStackRenderState;
+import net.minecraft.world.entity.EquipmentSlot;
+import net.minecraft.world.entity.LivingEntity;
+import net.minecraft.world.item.ItemDisplayContext;
+import org.spongepowered.asm.mixin.Final;
+import org.spongepowered.asm.mixin.Mixin;
+import org.spongepowered.asm.mixin.Shadow;
+import org.spongepowered.asm.mixin.Unique;
+import org.spongepowered.asm.mixin.injection.At;
+import org.spongepowered.asm.mixin.injection.Inject;
+import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
+
+@Mixin(LivingEntityRenderer.class)
+public class ReplaceHeadModel<T extends LivingEntity, S extends LivingEntityRenderState, M extends EntityModel<? super S>> {
+ @Shadow
+ @Final
+ protected ItemModelResolver itemModelResolver;
+
+ @Unique
+ private ItemStackRenderState tempRenderState = new ItemStackRenderState();
+
+ @Inject(
+ method = "extractRenderState(Lnet/minecraft/world/entity/LivingEntity;Lnet/minecraft/client/renderer/entity/state/LivingEntityRenderState;F)V",
+ at = @At("TAIL")
+ )
+ private void replaceHeadModel(
+ T livingEntity, S livingEntityRenderState, float f, CallbackInfo ci
+ ) {
+ var headItemStack = livingEntity.getItemBySlot(EquipmentSlot.HEAD);
+
+ HeadModelChooser.INSTANCE.getIS_CHOOSING_HEAD_MODEL().set(true);
+ tempRenderState.clear();
+ this.itemModelResolver.updateForLiving(tempRenderState, headItemStack, ItemDisplayContext.HEAD, livingEntity);
+ HeadModelChooser.INSTANCE.getIS_CHOOSING_HEAD_MODEL().set(false);
+
+ if (HeadModelChooser.HasExplicitHeadModelMarker.cast(tempRenderState)
+ .isExplicitHeadModel_Firmament()) {
+ livingEntityRenderState.wornHeadType = null;
+ var temp = livingEntityRenderState.headItem;
+ livingEntityRenderState.headItem = tempRenderState;
+ tempRenderState = temp;
+ }
+ }
+}
diff --git a/src/texturePacks/java/moe/nea/firmament/mixins/custommodels/ReplaceItemModelPatch.java b/src/texturePacks/java/moe/nea/firmament/mixins/custommodels/ReplaceItemModelPatch.java
index dfc87a0..87d4f13 100644
--- a/src/texturePacks/java/moe/nea/firmament/mixins/custommodels/ReplaceItemModelPatch.java
+++ b/src/texturePacks/java/moe/nea/firmament/mixins/custommodels/ReplaceItemModelPatch.java
@@ -4,46 +4,40 @@ 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.client.render.item.model.ItemModel;
-import net.minecraft.client.render.item.model.MissingItemModel;
-import net.minecraft.client.render.model.BakedModelManager;
-import net.minecraft.component.ComponentType;
-import net.minecraft.item.ItemStack;
-import net.minecraft.util.Identifier;
+import moe.nea.firmament.util.mc.IntrospectableItemModelManager;
+import net.minecraft.client.renderer.item.ItemModelResolver;
+import net.minecraft.client.renderer.item.ItemModel;
+import net.minecraft.client.renderer.item.MissingItemModel;
+import net.minecraft.core.component.DataComponentType;
+import net.minecraft.world.item.ItemStack;
+import net.minecraft.resources.ResourceLocation;
+import org.jetbrains.annotations.NotNull;
import org.spongepowered.asm.mixin.Final;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Shadow;
-import org.spongepowered.asm.mixin.Unique;
import org.spongepowered.asm.mixin.injection.At;
-import org.spongepowered.asm.mixin.injection.Inject;
-import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
import java.util.function.Function;
-@Mixin(ItemModelManager.class)
-public class ReplaceItemModelPatch {
+@Mixin(ItemModelResolver.class)
+public class ReplaceItemModelPatch implements IntrospectableItemModelManager {
@Shadow
@Final
- private Function<Identifier, ItemModel> modelGetter;
-
- @Inject(method = "<init>", at = @At("TAIL"))
- private void saveMissingModel(BakedModelManager bakedModelManager, CallbackInfo ci) {
- }
-
- @Unique
- private boolean hasModel(Identifier identifier) {
- return !(modelGetter.apply(identifier) instanceof MissingItemModel);
- }
+ private Function<ResourceLocation, ItemModel> modelGetter;
@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 && hasModel(override)) {
+ method = "appendItemLayers",
+ at = @At(value = "INVOKE", target = "Lnet/minecraft/world/item/ItemStack;get(Lnet/minecraft/core/component/DataComponentType;)Ljava/lang/Object;"))
+ private Object replaceItemModelByIdentifier(ItemStack instance, DataComponentType componentType, Operation<Object> original) {
+ var override = CustomItemModelEvent.getModelIdentifier(instance, this);
+ if (override != null && hasModel_firmament(override)) {
return override;
}
return original.call(instance, componentType);
}
+
+ @Override
+ public boolean hasModel_firmament(@NotNull ResourceLocation identifier) {
+ return !(modelGetter.apply(identifier) instanceof MissingItemModel);
+ }
}
diff --git a/src/texturePacks/java/moe/nea/firmament/mixins/custommodels/ReplaceTextColorInHandledScreen.java b/src/texturePacks/java/moe/nea/firmament/mixins/custommodels/ReplaceTextColorInHandledScreen.java
deleted file mode 100644
index e4834e9..0000000
--- a/src/texturePacks/java/moe/nea/firmament/mixins/custommodels/ReplaceTextColorInHandledScreen.java
+++ /dev/null
@@ -1,48 +0,0 @@
-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));
- }
-
-}
diff --git a/src/texturePacks/java/moe/nea/firmament/mixins/custommodels/ReplaceTextsInDrawContext.java b/src/texturePacks/java/moe/nea/firmament/mixins/custommodels/ReplaceTextsInDrawContext.java
new file mode 100644
index 0000000..9a568f7
--- /dev/null
+++ b/src/texturePacks/java/moe/nea/firmament/mixins/custommodels/ReplaceTextsInDrawContext.java
@@ -0,0 +1,55 @@
+package moe.nea.firmament.mixins.custommodels;
+
+import com.llamalad7.mixinextras.injector.ModifyExpressionValue;
+import moe.nea.firmament.features.texturepack.CustomTextReplacements;
+import net.minecraft.client.gui.GuiGraphics;
+import net.minecraft.network.chat.FormattedText;
+import net.minecraft.network.chat.Component;
+import org.spongepowered.asm.mixin.Mixin;
+import org.spongepowered.asm.mixin.injection.At;
+import org.spongepowered.asm.mixin.injection.ModifyVariable;
+
+import java.util.stream.Stream;
+
+@Mixin(GuiGraphics.class)
+public class ReplaceTextsInDrawContext {
+ // I HATE THIS SO MUCH WHY CANT I JUST OPERATE ON ORDEREDTEXTS!!!
+ // JUNE I WILL RIP ALL OF THIS OUT AND MAKE YOU REWRITE EVERYTHING
+ // TODO: be in a mood to rewrite this
+
+ @ModifyVariable(method = "drawString(Lnet/minecraft/client/gui/Font;Lnet/minecraft/network/chat/Component;IIIZ)V", at = @At("HEAD"), argsOnly = true)
+ private Component replaceTextInDrawText(Component text) {
+ return CustomTextReplacements.replaceText(text);
+ }
+
+ @ModifyVariable(method = "drawCenteredString(Lnet/minecraft/client/gui/Font;Lnet/minecraft/network/chat/Component;III)V", at = @At("HEAD"), argsOnly = true)
+ private Component replaceTextInDrawCenteredTextWithShadow(Component text) {
+ return CustomTextReplacements.replaceText(text);
+ }
+
+ @ModifyVariable(method = "drawWordWrap", at = @At("HEAD"), argsOnly = true)
+ private FormattedText replaceTextInDrawWrappedText(FormattedText stringVisitable) {
+ return stringVisitable instanceof Component text ? CustomTextReplacements.replaceText(text) : stringVisitable;
+ }
+
+ @ModifyExpressionValue(method = "setComponentTooltipForNextFrame(Lnet/minecraft/client/gui/Font;Ljava/util/List;IILnet/minecraft/resources/ResourceLocation;)V", at = @At(value = "INVOKE", target = "Ljava/util/List;stream()Ljava/util/stream/Stream;"))
+ private Stream<Component> replaceTextInDrawTooltipListText(Stream<Component> original) {
+ return original.map(CustomTextReplacements::replaceText);
+ }
+
+ @ModifyExpressionValue(method = "setTooltipForNextFrame(Lnet/minecraft/client/gui/Font;Ljava/util/List;Ljava/util/Optional;IILnet/minecraft/resources/ResourceLocation;)V", at = @At(value = "INVOKE", target = "Ljava/util/List;stream()Ljava/util/stream/Stream;"))
+ private Stream<Component> replaceTextInDrawTooltipListTextWithOptional(Stream<Component> original) {
+ return original.map(CustomTextReplacements::replaceText);
+ }
+
+ @ModifyVariable(method = "setTooltipForNextFrame(Lnet/minecraft/network/chat/Component;II)V", at = @At("HEAD"), argsOnly = true)
+ private Component replaceTextInDrawTooltipSingle(Component text) {
+ return CustomTextReplacements.replaceText(text);
+ }
+
+ @ModifyExpressionValue(method = "renderComponentHoverEffect", at = @At(value = "INVOKE", target = "Lnet/minecraft/network/chat/HoverEvent$ShowText;value()Lnet/minecraft/network/chat/Component;"))
+ private Component replaceShowTextInHover(Component text) {
+ return CustomTextReplacements.replaceText(text);
+ }
+
+}
diff --git a/src/texturePacks/java/moe/nea/firmament/mixins/custommodels/SupplyFakeModelPatch.java b/src/texturePacks/java/moe/nea/firmament/mixins/custommodels/SupplyFakeModelPatch.java
index 8d3b3f8..0165bea 100644
--- a/src/texturePacks/java/moe/nea/firmament/mixins/custommodels/SupplyFakeModelPatch.java
+++ b/src/texturePacks/java/moe/nea/firmament/mixins/custommodels/SupplyFakeModelPatch.java
@@ -4,23 +4,23 @@ import com.google.gson.JsonObject;
import com.llamalad7.mixinextras.injector.ModifyReturnValue;
import com.llamalad7.mixinextras.sugar.Local;
import moe.nea.firmament.Firmament;
+import moe.nea.firmament.features.texturepack.CustomSkyBlockTextures;
+import moe.nea.firmament.features.texturepack.HeadModelChooser;
import moe.nea.firmament.features.texturepack.PredicateModel;
import moe.nea.firmament.util.ErrorUtil;
-import net.minecraft.client.item.ItemAsset;
-import net.minecraft.client.item.ItemAssetsLoader;
-import net.minecraft.client.render.item.model.BasicItemModel;
-import net.minecraft.client.render.item.model.ItemModel;
-import net.minecraft.resource.Resource;
-import net.minecraft.resource.ResourceManager;
-import net.minecraft.resource.ResourcePack;
-import net.minecraft.util.Identifier;
+import net.minecraft.client.renderer.item.ClientItem;
+import net.minecraft.client.resources.model.ClientItemInfoLoader;
+import net.minecraft.client.renderer.item.BlockModelWrapper;
+import net.minecraft.client.renderer.item.ItemModel;
+import net.minecraft.server.packs.resources.Resource;
+import net.minecraft.server.packs.resources.ResourceManager;
+import net.minecraft.server.packs.PackResources;
+import net.minecraft.resources.ResourceLocation;
import org.spongepowered.asm.mixin.Mixin;
+import org.spongepowered.asm.mixin.Unique;
import org.spongepowered.asm.mixin.injection.At;
-import java.io.IOException;
import java.io.InputStreamReader;
-import java.util.ArrayList;
-import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@@ -30,63 +30,68 @@ import java.util.concurrent.Executor;
import java.util.stream.Collector;
import java.util.stream.Collectors;
-@Mixin(ItemAssetsLoader.class)
+@Mixin(ClientItemInfoLoader.class)
public class SupplyFakeModelPatch {
@ModifyReturnValue(
- method = "load",
+ method = "scheduleLoad",
at = @At("RETURN")
)
- private static CompletableFuture<ItemAssetsLoader.Result> injectFakeGeneratedModels(
- CompletableFuture<ItemAssetsLoader.Result> original,
+ private static CompletableFuture<ClientItemInfoLoader.LoadedClientInfos> injectFakeGeneratedModels(
+ CompletableFuture<ClientItemInfoLoader.LoadedClientInfos> original,
@Local(argsOnly = true) ResourceManager resourceManager,
@Local(argsOnly = true) Executor executor
) {
return original.thenCompose(oldModels -> CompletableFuture.supplyAsync(() -> supplyExtraModels(resourceManager, oldModels), executor));
}
- private static ItemAssetsLoader.Result supplyExtraModels(ResourceManager resourceManager, ItemAssetsLoader.Result oldModels) {
- Map<Identifier, ItemAsset> newModels = new HashMap<>(oldModels.contents());
- var resources = resourceManager.findResources(
+ @Unique
+ private static ClientItemInfoLoader.LoadedClientInfos supplyExtraModels(ResourceManager resourceManager, ClientItemInfoLoader.LoadedClientInfos oldModels) {
+ if (!CustomSkyBlockTextures.TConfig.INSTANCE.getEnableLegacyMinecraftCompat()) return oldModels;
+ Map<ResourceLocation, ClientItem> newModels = new HashMap<>(oldModels.contents());
+ var resources = resourceManager.listResources(
"models/item",
- id -> id.getNamespace().equals("firmskyblock")
- && id.getPath().endsWith(".json")
- && !id.getPath().substring("models/item/".length()).contains("/"));
- for (Map.Entry<Identifier, Resource> model : resources.entrySet()) {
+ id -> (id.getNamespace().equals("firmskyblock") || id.getNamespace().equals("cittofirmgenerated"))
+ && id.getPath().endsWith(".json"));
+ for (Map.Entry<ResourceLocation, Resource> model : resources.entrySet()) {
var resource = model.getValue();
var itemModelId = model.getKey().withPath(it -> it.substring("models/item/".length(), it.length() - ".json".length()));
- var genericModelId = itemModelId.withPrefixedPath("item/");
+ var genericModelId = itemModelId.withPrefix("item/");
+ var itemAssetId = itemModelId.withPrefix("items/");
// TODO: inject tint indexes based on the json data here
- ItemModel.Unbaked unbakedModel = new BasicItemModel.Unbaked(genericModelId, List.of());
+ ItemModel.Unbaked unbakedModel = new BlockModelWrapper.Unbaked(genericModelId, List.of());
// TODO: add a filter using the pack.mcmeta to opt out of this behaviour
- try (var is = resource.getInputStream()) {
+ try (var is = resource.open()) {
var jsonObject = Firmament.INSTANCE.getGson().fromJson(new InputStreamReader(is), JsonObject.class);
unbakedModel = PredicateModel.Unbaked.fromLegacyJson(jsonObject, unbakedModel);
+ unbakedModel = HeadModelChooser.Unbaked.fromLegacyJson(jsonObject, unbakedModel);
} catch (Exception e) {
ErrorUtil.INSTANCE.softError("Could not create resource for fake model supplication: " + model.getKey(), e);
}
- if (resourceManager.getResource(itemModelId)
- .map(Resource::getPack)
- .map(it -> isResourcePackNewer(resourceManager, it, resource.getPack()))
+ if (resourceManager.getResource(itemAssetId.withSuffix(".json"))
+ .map(Resource::source)
+ .map(it -> isResourcePackNewer(resourceManager, it, resource.source()))
.orElse(true)) {
- newModels.put(itemModelId, new ItemAsset(
+ newModels.put(itemModelId, new ClientItem(
unbakedModel,
- new ItemAsset.Properties(true)
+ new ClientItem.Properties(true, true)
));
}
}
- return new ItemAssetsLoader.Result(newModels);
+ return new ClientItemInfoLoader.LoadedClientInfos(newModels);
}
+ @Unique
private static boolean isResourcePackNewer(
- ResourceManager manager,
- ResourcePack null_, ResourcePack proposal) {
- var pack = manager.streamResourcePacks()
+ ResourceManager manager,
+ PackResources null_, PackResources proposal) {
+ var pack = manager.listPacks()
.filter(it -> it == null_ || it == proposal)
.collect(findLast());
- return pack.orElse(null) == proposal;
+ return pack.orElse(null_) != null_;
}
+ @Unique
private static <T> Collector<T, ?, Optional<T>> findLast() {
return Collectors.reducing(Optional.empty(), Optional::of,
(left, right) -> right.isPresent() ? right : left);
diff --git a/src/texturePacks/java/moe/nea/firmament/mixins/custommodels/screenlayouts/ExpandScreenBoundaries.java b/src/texturePacks/java/moe/nea/firmament/mixins/custommodels/screenlayouts/ExpandScreenBoundaries.java
new file mode 100644
index 0000000..3598d37
--- /dev/null
+++ b/src/texturePacks/java/moe/nea/firmament/mixins/custommodels/screenlayouts/ExpandScreenBoundaries.java
@@ -0,0 +1,21 @@
+package moe.nea.firmament.mixins.custommodels.screenlayouts;
+
+import moe.nea.firmament.features.texturepack.CustomScreenLayouts;
+import net.minecraft.client.gui.screens.inventory.AbstractContainerScreen;
+import net.minecraft.client.gui.screens.inventory.AbstractRecipeBookScreen;
+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({AbstractContainerScreen.class, AbstractRecipeBookScreen.class})
+public class ExpandScreenBoundaries {
+ @Inject(method = "hasClickedOutside", at = @At("HEAD"), cancellable = true)
+ private void onClickOutsideBounds(double mouseX, double mouseY, int left, int top, CallbackInfoReturnable<Boolean> cir) {
+ var background = CustomScreenLayouts.getMover(CustomScreenLayouts.CustomScreenLayout::getBackground);
+ if (background == null) return;
+ var x = background.getX() + left;
+ var y = background.getY() + top;
+ cir.setReturnValue(mouseX < (double) x || mouseY < (double) y || mouseX >= (double) (x + background.getWidth()) || mouseY >= (double) (y + background.getHeight()));
+ }
+}
diff --git a/src/texturePacks/java/moe/nea/firmament/mixins/custommodels/screenlayouts/MoveSignElements.java b/src/texturePacks/java/moe/nea/firmament/mixins/custommodels/screenlayouts/MoveSignElements.java
new file mode 100644
index 0000000..0475a8b
--- /dev/null
+++ b/src/texturePacks/java/moe/nea/firmament/mixins/custommodels/screenlayouts/MoveSignElements.java
@@ -0,0 +1,70 @@
+package moe.nea.firmament.mixins.custommodels.screenlayouts;
+
+import com.llamalad7.mixinextras.expression.Definition;
+import com.llamalad7.mixinextras.expression.Expression;
+import com.llamalad7.mixinextras.injector.v2.WrapWithCondition;
+import com.llamalad7.mixinextras.injector.wrapoperation.Operation;
+import com.llamalad7.mixinextras.injector.wrapoperation.WrapOperation;
+import com.llamalad7.mixinextras.sugar.Local;
+import com.llamalad7.mixinextras.sugar.Share;
+import moe.nea.firmament.features.texturepack.CustomScreenLayouts;
+import net.minecraft.client.gui.Font;
+import net.minecraft.client.gui.GuiGraphics;
+import net.minecraft.client.gui.screens.inventory.AbstractSignEditScreen;
+import org.spongepowered.asm.mixin.Mixin;
+import org.spongepowered.asm.mixin.injection.At;
+
+@Mixin(AbstractSignEditScreen.class)
+public class MoveSignElements {
+ @WrapWithCondition(
+ method = "renderSign",
+ at = @At(value = "INVOKE", target = "Lnet/minecraft/client/gui/screens/inventory/AbstractSignEditScreen;renderSignBackground(Lnet/minecraft/client/gui/GuiGraphics;)V"))
+ private boolean onDrawBackgroundSign(AbstractSignEditScreen instance, GuiGraphics drawContext) {
+ final var override = CustomScreenLayouts.getActiveScreenOverride();
+ if (override == null || override.getBackground() == null) return true;
+ override.getBackground().renderDirect(drawContext);
+ return false;
+ }
+
+ @WrapOperation(method = "renderSignText", at = {
+ @At(value = "INVOKE", target = "Lnet/minecraft/client/gui/GuiGraphics;textHighlight(IIII)V")}
+ )
+ private void onRenderSignTextSelection(
+ GuiGraphics instance, int x1, int y1, int x2, int y2, Operation<Void> original,
+ @Local(index = 9) int messageIndex) {
+ instance.pose().pushMatrix();
+ final var override = CustomScreenLayouts.getSignTextMover(messageIndex);
+ if (override != null) {
+ instance.pose().translate(override.getX(), override.getY());
+ }
+ original.call(instance, x1, y1, x2, y2);
+ instance.pose().popMatrix();
+ }
+ @WrapOperation(method = "renderSignText", at = {
+ @At(value = "INVOKE", target = "Lnet/minecraft/client/gui/GuiGraphics;fill(IIIII)V")}
+ )
+ private void onRenderSignTextFill(
+ GuiGraphics instance, int x1, int y1, int x2, int y2, int color, Operation<Void> original, @Local(index = 9) int messageIndex) {
+ instance.pose().pushMatrix();
+ final var override = CustomScreenLayouts.getSignTextMover(messageIndex);
+ if (override != null) {
+ instance.pose().translate(override.getX(), override.getY());
+ }
+ original.call(instance, x1, y1, x2, y2, color);
+ instance.pose().popMatrix();
+ }
+
+ @WrapOperation(method = "renderSignText", at = {
+ @At(value = "INVOKE", target = "Lnet/minecraft/client/gui/GuiGraphics;drawString(Lnet/minecraft/client/gui/Font;Ljava/lang/String;IIIZ)V")},
+ expect = 2)
+ private void onRenderSignTextRendering(GuiGraphics instance, Font textRenderer, String text, int x, int y, int color, boolean shadow, Operation<Void> original, @Local(index = 9) int messageIndex) {
+ instance.pose().pushMatrix();
+ final var override = CustomScreenLayouts.getSignTextMover(messageIndex);
+ if (override != null) {
+ instance.pose().translate(override.getX(), override.getY());
+ }
+ original.call(instance, textRenderer, text, x, y, color, shadow);
+ instance.pose().popMatrix();
+ }
+
+}
diff --git a/src/texturePacks/java/moe/nea/firmament/mixins/custommodels/screenlayouts/ReplaceAnvilScreen.java b/src/texturePacks/java/moe/nea/firmament/mixins/custommodels/screenlayouts/ReplaceAnvilScreen.java
new file mode 100644
index 0000000..691c692
--- /dev/null
+++ b/src/texturePacks/java/moe/nea/firmament/mixins/custommodels/screenlayouts/ReplaceAnvilScreen.java
@@ -0,0 +1,55 @@
+package moe.nea.firmament.mixins.custommodels.screenlayouts;
+
+import com.llamalad7.mixinextras.injector.wrapoperation.Operation;
+import com.llamalad7.mixinextras.injector.wrapoperation.WrapOperation;
+import moe.nea.firmament.features.texturepack.CustomScreenLayouts;
+import net.minecraft.client.gui.Font;
+import net.minecraft.client.gui.GuiGraphics;
+import net.minecraft.client.gui.screens.inventory.AnvilScreen;
+import net.minecraft.client.gui.screens.inventory.ItemCombinerScreen;
+import net.minecraft.client.gui.components.EditBox;
+import net.minecraft.world.entity.player.Inventory;
+import net.minecraft.world.inventory.AnvilMenu;
+import net.minecraft.network.chat.Component;
+import net.minecraft.resources.ResourceLocation;
+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;
+
+@Mixin(AnvilScreen.class)
+public abstract class ReplaceAnvilScreen extends ItemCombinerScreen<AnvilMenu> {
+ @Shadow
+ private EditBox name;
+
+ public ReplaceAnvilScreen(AnvilMenu handler, Inventory playerInventory, Component title, ResourceLocation texture) {
+ super(handler, playerInventory, title, texture);
+ }
+
+ @Inject(method = "subInit", at = @At("TAIL"))
+ private void moveNameField(CallbackInfo ci) {
+ var override = CustomScreenLayouts.getMover(CustomScreenLayouts.CustomScreenLayout::getNameField);
+ if (override == null) return;
+ int baseX = (this.width - this.imageWidth) / 2;
+ int baseY = (this.height - this.imageHeight) / 2;
+ name.setX(baseX + override.getX());
+ name.setY(baseY + override.getY());
+ if (override.getWidth() != null)
+ name.setWidth(override.getWidth());
+ if (override.getHeight() != null)
+ name.setHeight(override.getHeight());
+ }
+
+ @WrapOperation(method = "renderLabels",
+ at = @At(value = "INVOKE", target = "Lnet/minecraft/client/gui/GuiGraphics;drawString(Lnet/minecraft/client/gui/Font;Lnet/minecraft/network/chat/Component;III)V"),
+ allow = 1)
+ private void onDrawRepairCost(GuiGraphics instance, Font textRenderer, Component text, int x, int y, int color, Operation<Void> original) {
+ var textOverride = CustomScreenLayouts.getTextMover(CustomScreenLayouts.CustomScreenLayout::getRepairCostTitle);
+ original.call(instance, textRenderer,
+ textOverride.replaceText(text),
+ textOverride.replaceX(textRenderer, text, x),
+ textOverride.replaceY(y),
+ textOverride.replaceColor(text, color));
+ }
+}
diff --git a/src/texturePacks/java/moe/nea/firmament/mixins/custommodels/screenlayouts/ReplaceForgingScreen.java b/src/texturePacks/java/moe/nea/firmament/mixins/custommodels/screenlayouts/ReplaceForgingScreen.java
new file mode 100644
index 0000000..8b07fec
--- /dev/null
+++ b/src/texturePacks/java/moe/nea/firmament/mixins/custommodels/screenlayouts/ReplaceForgingScreen.java
@@ -0,0 +1,9 @@
+package moe.nea.firmament.mixins.custommodels.screenlayouts;
+
+import net.minecraft.client.gui.screens.inventory.ItemCombinerScreen;
+import org.spongepowered.asm.mixin.Mixin;
+import org.spongepowered.asm.mixin.injection.Inject;
+
+@Mixin(ItemCombinerScreen.class)
+public class ReplaceForgingScreen {
+}
diff --git a/src/texturePacks/java/moe/nea/firmament/mixins/custommodels/screenlayouts/ReplaceFurnaceBackgrounds.java b/src/texturePacks/java/moe/nea/firmament/mixins/custommodels/screenlayouts/ReplaceFurnaceBackgrounds.java
new file mode 100644
index 0000000..d02085a
--- /dev/null
+++ b/src/texturePacks/java/moe/nea/firmament/mixins/custommodels/screenlayouts/ReplaceFurnaceBackgrounds.java
@@ -0,0 +1,32 @@
+package moe.nea.firmament.mixins.custommodels.screenlayouts;
+
+import com.llamalad7.mixinextras.injector.v2.WrapWithCondition;
+import com.mojang.blaze3d.pipeline.RenderPipeline;
+import moe.nea.firmament.features.texturepack.CustomScreenLayouts;
+import net.minecraft.client.gui.GuiGraphics;
+import net.minecraft.client.gui.screens.inventory.AbstractFurnaceScreen;
+import net.minecraft.client.gui.screens.inventory.AbstractRecipeBookScreen;
+import net.minecraft.client.gui.screens.recipebook.RecipeBookComponent;
+import net.minecraft.client.renderer.RenderType;
+import net.minecraft.world.entity.player.Inventory;
+import net.minecraft.world.inventory.AbstractFurnaceMenu;
+import net.minecraft.network.chat.Component;
+import net.minecraft.resources.ResourceLocation;
+import org.spongepowered.asm.mixin.Mixin;
+import org.spongepowered.asm.mixin.injection.At;
+import java.util.function.Function;
+
+@Mixin(AbstractFurnaceScreen.class)
+public abstract class ReplaceFurnaceBackgrounds<T extends AbstractFurnaceMenu> extends AbstractRecipeBookScreen<T> {
+ public ReplaceFurnaceBackgrounds(T handler, RecipeBookComponent<?> recipeBook, Inventory inventory, Component title) {
+ super(handler, recipeBook, inventory, title);
+ }
+
+ @WrapWithCondition(method = "renderBg", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/gui/GuiGraphics;blit(Lcom/mojang/blaze3d/pipeline/RenderPipeline;Lnet/minecraft/resources/ResourceLocation;IIFFIIII)V"), allow = 1)
+ private boolean onDrawBackground(GuiGraphics instance, RenderPipeline pipeline, ResourceLocation sprite, int x, int y, float u, float v, int width, int height, int textureWidth, int textureHeight) {
+ final var override = CustomScreenLayouts.getActiveScreenOverride();
+ if (override == null || override.getBackground() == null) return true;
+ override.getBackground().renderGeneric(instance, this);
+ return false;
+ }
+}
diff --git a/src/texturePacks/java/moe/nea/firmament/mixins/custommodels/screenlayouts/ReplaceGenericBackgrounds.java b/src/texturePacks/java/moe/nea/firmament/mixins/custommodels/screenlayouts/ReplaceGenericBackgrounds.java
new file mode 100644
index 0000000..ec6cfc6
--- /dev/null
+++ b/src/texturePacks/java/moe/nea/firmament/mixins/custommodels/screenlayouts/ReplaceGenericBackgrounds.java
@@ -0,0 +1,28 @@
+package moe.nea.firmament.mixins.custommodels.screenlayouts;
+
+import moe.nea.firmament.features.texturepack.CustomScreenLayouts;
+import net.minecraft.client.gui.GuiGraphics;
+import net.minecraft.client.gui.screens.inventory.*;
+import net.minecraft.world.entity.player.Inventory;
+import net.minecraft.world.inventory.AbstractContainerMenu;
+import net.minecraft.network.chat.Component;
+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.CallbackInfo;
+
+@Mixin({CraftingScreen.class, CrafterScreen.class, DispenserScreen.class, ContainerScreen.class, HopperScreen.class, ShulkerBoxScreen.class,})
+public abstract class ReplaceGenericBackgrounds extends AbstractContainerScreen<AbstractContainerMenu> {
+ // TODO: split out screens with special background components like flames, arrows, etc. (maybe arrows deserve generic handling tho)
+ public ReplaceGenericBackgrounds(AbstractContainerMenu handler, Inventory inventory, Component title) {
+ super(handler, inventory, title);
+ }
+
+ @Inject(method = "renderBg", at = @At("HEAD"), cancellable = true)
+ private void replaceDrawBackground(GuiGraphics context, float deltaTicks, int mouseX, int mouseY, CallbackInfo ci) {
+ final var override = CustomScreenLayouts.getActiveScreenOverride();
+ if (override == null || override.getBackground() == null) return;
+ override.getBackground().renderGeneric(context, this);
+ ci.cancel();
+ }
+}
diff --git a/src/texturePacks/java/moe/nea/firmament/mixins/custommodels/screenlayouts/ReplacePlayerBackgrounds.java b/src/texturePacks/java/moe/nea/firmament/mixins/custommodels/screenlayouts/ReplacePlayerBackgrounds.java
new file mode 100644
index 0000000..d089d6d
--- /dev/null
+++ b/src/texturePacks/java/moe/nea/firmament/mixins/custommodels/screenlayouts/ReplacePlayerBackgrounds.java
@@ -0,0 +1,51 @@
+package moe.nea.firmament.mixins.custommodels.screenlayouts;
+
+import com.llamalad7.mixinextras.injector.v2.WrapWithCondition;
+import com.llamalad7.mixinextras.injector.wrapoperation.Operation;
+import com.llamalad7.mixinextras.injector.wrapoperation.WrapOperation;
+import com.mojang.blaze3d.pipeline.RenderPipeline;
+import moe.nea.firmament.features.texturepack.CustomScreenLayouts;
+import net.minecraft.client.gui.Font;
+import net.minecraft.client.gui.GuiGraphics;
+import net.minecraft.client.gui.screens.inventory.InventoryScreen;
+import net.minecraft.client.gui.screens.inventory.AbstractRecipeBookScreen;
+import net.minecraft.client.gui.screens.recipebook.RecipeBookComponent;
+import net.minecraft.client.renderer.RenderType;
+import net.minecraft.world.entity.player.Inventory;
+import net.minecraft.world.inventory.InventoryMenu;
+import net.minecraft.network.chat.Component;
+import net.minecraft.resources.ResourceLocation;
+import org.spongepowered.asm.mixin.Mixin;
+import org.spongepowered.asm.mixin.injection.At;
+
+import java.util.function.Function;
+
+@Mixin(InventoryScreen.class)
+public abstract class ReplacePlayerBackgrounds extends AbstractRecipeBookScreen<InventoryMenu> {
+ public ReplacePlayerBackgrounds(InventoryMenu handler, RecipeBookComponent<?> recipeBook, Inventory inventory, Component title) {
+ super(handler, recipeBook, inventory, title);
+ }
+
+
+ @WrapOperation(method = "renderLabels",
+ allow = 1,
+ at = @At(value = "INVOKE", target = "Lnet/minecraft/client/gui/GuiGraphics;drawString(Lnet/minecraft/client/gui/Font;Lnet/minecraft/network/chat/Component;IIIZ)V"))
+ private void onDrawForegroundText(GuiGraphics instance, Font textRenderer, Component text, int x, int y, int color, boolean shadow, Operation<Void> original) {
+ var textOverride = CustomScreenLayouts.getTextMover(CustomScreenLayouts.CustomScreenLayout::getContainerTitle);
+ original.call(instance, textRenderer,
+ textOverride.replaceText(text),
+ textOverride.replaceX(textRenderer, text, x),
+ textOverride.replaceY(y),
+ textOverride.replaceColor(text, color),
+ shadow);
+ }
+
+ @WrapWithCondition(method = "renderBg", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/gui/GuiGraphics;blit(Lcom/mojang/blaze3d/pipeline/RenderPipeline;Lnet/minecraft/resources/ResourceLocation;IIFFIIII)V"))
+ private boolean onDrawBackground(GuiGraphics instance, RenderPipeline pipeline, ResourceLocation sprite, int x, int y, float u, float v, int width, int height, int textureWidth, int textureHeight) {
+ final var override = CustomScreenLayouts.getActiveScreenOverride();
+ if (override == null || override.getBackground() == null) return true;
+ override.getBackground().renderGeneric(instance, this);
+ return false;
+ }
+ // TODO: allow moving the player
+}
diff --git a/src/texturePacks/java/moe/nea/firmament/mixins/custommodels/screenlayouts/ReplaceTextColorInHandledScreen.java b/src/texturePacks/java/moe/nea/firmament/mixins/custommodels/screenlayouts/ReplaceTextColorInHandledScreen.java
new file mode 100644
index 0000000..6dbbbbe
--- /dev/null
+++ b/src/texturePacks/java/moe/nea/firmament/mixins/custommodels/screenlayouts/ReplaceTextColorInHandledScreen.java
@@ -0,0 +1,60 @@
+package moe.nea.firmament.mixins.custommodels.screenlayouts;
+
+import com.llamalad7.mixinextras.injector.wrapoperation.Operation;
+import com.llamalad7.mixinextras.injector.wrapoperation.WrapOperation;
+import moe.nea.firmament.features.texturepack.CustomScreenLayouts;
+import net.minecraft.client.gui.Font;
+import net.minecraft.client.gui.GuiGraphics;
+import net.minecraft.client.gui.screens.inventory.AbstractContainerScreen;
+import net.minecraft.network.chat.Component;
+import org.objectweb.asm.Opcodes;
+import org.spongepowered.asm.mixin.Mixin;
+import org.spongepowered.asm.mixin.injection.At;
+import org.spongepowered.asm.mixin.injection.Slice;
+
+@Mixin(AbstractContainerScreen.class)
+// TODO: MerchantScreen.class, BeaconScreen.class
+public class ReplaceTextColorInHandledScreen {
+
+ @WrapOperation(
+ method = "renderLabels",
+ at = @At(
+ value = "INVOKE",
+ target = "Lnet/minecraft/client/gui/GuiGraphics;drawString(Lnet/minecraft/client/gui/Font;Lnet/minecraft/network/chat/Component;IIIZ)V"),
+ slice = @Slice(
+ from = @At(value = "FIELD", target = "Lnet/minecraft/client/gui/screens/inventory/AbstractContainerScreen;title:Lnet/minecraft/network/chat/Component;", opcode = Opcodes.GETFIELD),
+ to = @At(value = "FIELD", target = "Lnet/minecraft/client/gui/screens/inventory/AbstractContainerScreen;playerInventoryTitle:Lnet/minecraft/network/chat/Component;", opcode = Opcodes.GETFIELD)
+ ),
+ allow = 1,
+ require = 1)
+ private void replaceContainerTitle(GuiGraphics instance, Font textRenderer, Component text, int x, int y, int color, boolean shadow, Operation<Void> original) {
+ var textOverride = CustomScreenLayouts.getTextMover(CustomScreenLayouts.CustomScreenLayout::getContainerTitle);
+ original.call(instance, textRenderer,
+ textOverride.replaceText(text),
+ textOverride.replaceX(textRenderer, text, x),
+ textOverride.replaceY(y),
+ textOverride.replaceColor(text, color),
+ shadow);
+ }
+
+ @WrapOperation(
+ method = "renderLabels",
+ at = @At(
+ value = "INVOKE",
+ target = "Lnet/minecraft/client/gui/GuiGraphics;drawString(Lnet/minecraft/client/gui/Font;Lnet/minecraft/network/chat/Component;IIIZ)V"),
+ slice = @Slice(
+ from = @At(value = "FIELD", target = "Lnet/minecraft/client/gui/screens/inventory/AbstractContainerScreen;playerInventoryTitle:Lnet/minecraft/network/chat/Component;", opcode = Opcodes.GETFIELD),
+ to = @At(value = "TAIL")
+ ),
+ allow = 1,
+ require = 1)
+ private void replacePlayerTitle(GuiGraphics instance, Font textRenderer, Component text, int x, int y, int color, boolean shadow, Operation<Void> original) {
+ var textOverride = CustomScreenLayouts.getTextMover(CustomScreenLayouts.CustomScreenLayout::getPlayerTitle);
+ original.call(instance, textRenderer,
+ textOverride.replaceText(text),
+ textOverride.replaceX(textRenderer, text, x),
+ textOverride.replaceY(y),
+ textOverride.replaceColor(text, color),
+ shadow);
+ }
+}