diff options
Diffstat (limited to 'src/texturePacks/java/moe/nea')
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); + } +} |
