From 7682534f6fd139b75f24c79b76098fe05f0fa0fe Mon Sep 17 00:00:00 2001 From: Linnea Gräf Date: Sat, 11 May 2024 03:28:05 +0200 Subject: Add custom global textures --- docs/Texture Pack Format.md | 46 ++++++ .../firmament/mixins/CustomModelBakerPatch.java | 9 +- .../firmament/mixins/EarlyResourceReloadPatch.java | 30 ++++ .../mixins/ResourceReloaderRegistrationPatch.java | 31 ++++ .../custommodels/GlobalModelOverridePatch.java | 34 +++++ src/main/kotlin/moe/nea/firmament/Firmament.kt | 26 +++- .../nea/firmament/events/BakeExtraModelsEvent.kt | 21 +++ .../moe/nea/firmament/events/ClientStartedEvent.kt | 11 ++ .../firmament/events/EarlyResourceReloadEvent.kt | 15 ++ .../events/FinalizeResourceManagerEvent.kt | 15 ++ .../features/texturepack/CustomGlobalTextures.kt | 161 +++++++++++++++++++++ .../texturepack/CustomModelOverrideParser.kt | 18 +++ .../features/texturepack/CustomSkyBlockTextures.kt | 18 +++ .../features/texturepack/StringMatcher.kt | 85 +++++++++++ .../moe/nea/firmament/util/IdentifierSerializer.kt | 28 ++++ .../kotlin/moe/nea/firmament/util/LogIfNull.kt | 13 ++ .../assets/firmament/filters/screen/always.json | 5 + .../firmament/filters/screen/always.json.license | 3 + src/main/resources/assets/firmament/icon.png | Bin 20279 -> 0 bytes .../resources/assets/firmament/icon.png.license | 3 - 20 files changed, 556 insertions(+), 16 deletions(-) create mode 100644 src/main/java/moe/nea/firmament/mixins/EarlyResourceReloadPatch.java create mode 100644 src/main/java/moe/nea/firmament/mixins/ResourceReloaderRegistrationPatch.java create mode 100644 src/main/java/moe/nea/firmament/mixins/custommodels/GlobalModelOverridePatch.java create mode 100644 src/main/kotlin/moe/nea/firmament/events/BakeExtraModelsEvent.kt create mode 100644 src/main/kotlin/moe/nea/firmament/events/ClientStartedEvent.kt create mode 100644 src/main/kotlin/moe/nea/firmament/events/EarlyResourceReloadEvent.kt create mode 100644 src/main/kotlin/moe/nea/firmament/events/FinalizeResourceManagerEvent.kt create mode 100644 src/main/kotlin/moe/nea/firmament/features/texturepack/CustomGlobalTextures.kt create mode 100644 src/main/kotlin/moe/nea/firmament/util/IdentifierSerializer.kt create mode 100644 src/main/kotlin/moe/nea/firmament/util/LogIfNull.kt create mode 100644 src/main/resources/assets/firmament/filters/screen/always.json create mode 100644 src/main/resources/assets/firmament/filters/screen/always.json.license delete mode 100644 src/main/resources/assets/firmament/icon.png delete mode 100644 src/main/resources/assets/firmament/icon.png.license diff --git a/docs/Texture Pack Format.md b/docs/Texture Pack Format.md index 5e466bf..bebcbb4 100644 --- a/docs/Texture Pack Format.md +++ b/docs/Texture Pack Format.md @@ -157,3 +157,49 @@ armor you can use `assets/firmskyblock/textures/models/armor/{skyblock_id}_layer regular leather colors. If you want the leather colors to be applied, supply the normal non-`_overlay` variant, and also supply a blank `_overlay` variant. You can also put non-color-affected parts into the `_overlay` variant. +## Global Item Texture Replacement + +Most texture replacement is done based on the SkyBlock id of the item. However, some items you might want to re-texture +do not have an id. The next best alternative you had before was just to replace the vanilla item and add a bunch of +predicates. This tries to fix this problem, at the cost of being more performance intensive than the other re-texturing +methods. + +The entrypoint to global overrides is `firmskyblock:overrides/item`. Put your overrides into that folder, with one file +per override. + +```json5 +{ + "screen": "testrp:chocolate_factory", + "model": "testrp:time_tower", + "predicate": { + "firmament:display_name": { + "regex": "Time Tower.*" + } + } +} +``` + +There are three parts to the override. + +The `model` is an *item id* that the item will be replaced with. This means the +model will be loaded from `assets//models/item/.json`. Make sure to use your own namespace to +avoid collisions with other texture packs that might use the same id for a gui. + +The `predicate` is just a normal [predicate](#predicates). This one does not support the vanilla predicates. You can +still use vanilla predicates in the resolved model, but this will not allow you to fall back to other global overrides. + +### Global item texture Screens + +In order to improve performance not all overrides are tested all the time. Instead you can prefilter by the screen that +is open. First the gui is resolved to `assets//filters/screen/.json`. Make sure to use your own namespace +to avoid collisions with other texture packs that might use the same id for a screen. + +```json +{ + "title": "Chocolate Factory" +} +``` + +Currently, the only supported filter is `title`, which accepts a [string matcher](#string-matcher). You can also use +`firmament:always` as an always on filter (this is the recommended way). + diff --git a/src/main/java/moe/nea/firmament/mixins/CustomModelBakerPatch.java b/src/main/java/moe/nea/firmament/mixins/CustomModelBakerPatch.java index 2da4176..6bd9ba1 100644 --- a/src/main/java/moe/nea/firmament/mixins/CustomModelBakerPatch.java +++ b/src/main/java/moe/nea/firmament/mixins/CustomModelBakerPatch.java @@ -1,11 +1,13 @@ /* * SPDX-FileCopyrightText: 2023 Linnea Gräf + * SPDX-FileCopyrightText: 2024 Linnea Gräf * * SPDX-License-Identifier: GPL-3.0-or-later */ package moe.nea.firmament.mixins; +import moe.nea.firmament.events.BakeExtraModelsEvent; import net.minecraft.client.MinecraftClient; import net.minecraft.client.render.model.ModelLoader; import net.minecraft.client.render.model.UnbakedModel; @@ -39,12 +41,7 @@ public abstract class CustomModelBakerPatch { @Inject(method = "bake", at = @At("HEAD")) public void onBake(BiFunction spriteLoader, CallbackInfo ci) { - Map resources = - MinecraftClient.getInstance().getResourceManager().findResources("models/item", it -> "firmskyblock".equals(it.getNamespace()) && it.getPath().endsWith(".json")); - for (Identifier identifier : resources.keySet()) { - ModelIdentifier modelId = new ModelIdentifier("firmskyblock", identifier.getPath().substring("models/item/".length(), identifier.getPath().length() - ".json".length()), "inventory"); - addModel(modelId); - } + BakeExtraModelsEvent.Companion.publish(new BakeExtraModelsEvent(this::addModel)); modelsToBake.values().forEach(model -> model.setParents(this::getOrLoadModel)); } } diff --git a/src/main/java/moe/nea/firmament/mixins/EarlyResourceReloadPatch.java b/src/main/java/moe/nea/firmament/mixins/EarlyResourceReloadPatch.java new file mode 100644 index 0000000..144eda1 --- /dev/null +++ b/src/main/java/moe/nea/firmament/mixins/EarlyResourceReloadPatch.java @@ -0,0 +1,30 @@ +/* + * SPDX-FileCopyrightText: 2024 Linnea Gräf + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +package moe.nea.firmament.mixins; + +import moe.nea.firmament.events.EarlyResourceReloadEvent; +import net.minecraft.resource.ReloadableResourceManagerImpl; +import net.minecraft.resource.ResourceManager; +import net.minecraft.resource.ResourcePack; +import net.minecraft.resource.ResourceReload; +import net.minecraft.util.Unit; +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.List; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.Executor; + +@Mixin(ReloadableResourceManagerImpl.class) +public abstract class EarlyResourceReloadPatch implements ResourceManager { + @Inject(method = "reload", at = @At(value = "INVOKE", target = "Lnet/minecraft/resource/SimpleResourceReload;start(Lnet/minecraft/resource/ResourceManager;Ljava/util/List;Ljava/util/concurrent/Executor;Ljava/util/concurrent/Executor;Ljava/util/concurrent/CompletableFuture;Z)Lnet/minecraft/resource/ResourceReload;", shift = At.Shift.BEFORE)) + public void onResourceReload(Executor prepareExecutor, Executor applyExecutor, CompletableFuture initialStage, List packs, CallbackInfoReturnable cir) { + EarlyResourceReloadEvent.Companion.publish(new EarlyResourceReloadEvent(this, prepareExecutor)); + } +} diff --git a/src/main/java/moe/nea/firmament/mixins/ResourceReloaderRegistrationPatch.java b/src/main/java/moe/nea/firmament/mixins/ResourceReloaderRegistrationPatch.java new file mode 100644 index 0000000..bf0b925 --- /dev/null +++ b/src/main/java/moe/nea/firmament/mixins/ResourceReloaderRegistrationPatch.java @@ -0,0 +1,31 @@ +/* + * SPDX-FileCopyrightText: 2024 Linnea Gräf + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +package moe.nea.firmament.mixins; + +import moe.nea.firmament.events.FinalizeResourceManagerEvent; +import net.minecraft.client.MinecraftClient; +import net.minecraft.client.RunArgs; +import net.minecraft.resource.ReloadableResourceManagerImpl; +import org.spongepowered.asm.mixin.Final; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Shadow; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; + +@Mixin(MinecraftClient.class) +public class ResourceReloaderRegistrationPatch { + @Shadow + @Final + private ReloadableResourceManagerImpl resourceManager; + + @Inject(method = "", at = @At(value = "INVOKE", target = "Lnet/minecraft/resource/ResourcePackManager;createResourcePacks()Ljava/util/List;", shift = At.Shift.BEFORE)) + private void onBeforeResourcePackCreation(RunArgs args, CallbackInfo ci) { + FinalizeResourceManagerEvent.Companion.publish(new FinalizeResourceManagerEvent(this.resourceManager)); + } +} + diff --git a/src/main/java/moe/nea/firmament/mixins/custommodels/GlobalModelOverridePatch.java b/src/main/java/moe/nea/firmament/mixins/custommodels/GlobalModelOverridePatch.java new file mode 100644 index 0000000..ec1d09c --- /dev/null +++ b/src/main/java/moe/nea/firmament/mixins/custommodels/GlobalModelOverridePatch.java @@ -0,0 +1,34 @@ +/* + * SPDX-FileCopyrightText: 2024 Linnea Gräf + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +package moe.nea.firmament.mixins.custommodels; + +import moe.nea.firmament.features.texturepack.CustomGlobalTextures; +import net.minecraft.client.render.item.ItemModels; +import net.minecraft.client.render.item.ItemRenderer; +import net.minecraft.client.render.model.BakedModel; +import net.minecraft.entity.LivingEntity; +import net.minecraft.item.ItemStack; +import net.minecraft.world.World; +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(ItemRenderer.class) +public abstract class GlobalModelOverridePatch { + + @Shadow + public abstract ItemModels getModels(); + + @Inject(method = "getModel", at = @At("HEAD"), cancellable = true) + private void overrideGlobalModel( + ItemStack stack, World world, LivingEntity entity, + int seed, CallbackInfoReturnable cir) { + CustomGlobalTextures.replaceGlobalModel(this.getModels(), stack, cir); + } +} diff --git a/src/main/kotlin/moe/nea/firmament/Firmament.kt b/src/main/kotlin/moe/nea/firmament/Firmament.kt index 03d4576..630950a 100644 --- a/src/main/kotlin/moe/nea/firmament/Firmament.kt +++ b/src/main/kotlin/moe/nea/firmament/Firmament.kt @@ -8,13 +8,15 @@ package moe.nea.firmament import com.mojang.brigadier.CommandDispatcher -import io.ktor.client.* -import io.ktor.client.plugins.* -import io.ktor.client.plugins.cache.* -import io.ktor.client.plugins.compression.* -import io.ktor.client.plugins.contentnegotiation.* -import io.ktor.client.plugins.logging.* -import io.ktor.serialization.kotlinx.json.* +import io.ktor.client.HttpClient +import io.ktor.client.plugins.UserAgent +import io.ktor.client.plugins.cache.HttpCache +import io.ktor.client.plugins.compression.ContentEncoding +import io.ktor.client.plugins.contentnegotiation.ContentNegotiation +import io.ktor.client.plugins.logging.LogLevel +import io.ktor.client.plugins.logging.Logging +import io.ktor.serialization.kotlinx.json.json +import java.io.InputStream import java.nio.file.Files import java.nio.file.Path import net.fabricmc.fabric.api.client.command.v2.ClientCommandRegistrationCallback @@ -36,11 +38,13 @@ import kotlinx.coroutines.Job import kotlinx.coroutines.SupervisorJob import kotlinx.coroutines.plus import kotlinx.serialization.json.Json +import kotlinx.serialization.json.decodeFromStream import kotlin.coroutines.EmptyCoroutineContext import net.minecraft.command.CommandRegistryAccess import net.minecraft.util.Identifier import moe.nea.firmament.commands.registerFirmamentCommand import moe.nea.firmament.dbus.FirmamentDbusObject +import moe.nea.firmament.events.ClientStartedEvent import moe.nea.firmament.events.CommandEvent import moe.nea.firmament.events.ItemTooltipEvent import moe.nea.firmament.events.ScreenRenderPostEvent @@ -133,6 +137,9 @@ object Firmament { FeatureManager.autoload() HypixelStaticData.spawnDataCollectionLoop() ClientCommandRegistrationCallback.EVENT.register(this::registerCommands) + ClientLifecycleEvents.CLIENT_STARTED.register(ClientLifecycleEvents.ClientStarted { + ClientStartedEvent.publish(ClientStartedEvent()) + }) ClientLifecycleEvents.CLIENT_STOPPING.register(ClientLifecycleEvents.ClientStopping { logger.info("Shutting down Firmament coroutines") globalJob.cancel() @@ -151,4 +158,9 @@ object Firmament { fun identifier(path: String) = Identifier(MOD_ID, path) + inline fun tryDecodeJsonFromStream(inputStream: InputStream): Result { + return runCatching { + json.decodeFromStream(inputStream) + } + } } diff --git a/src/main/kotlin/moe/nea/firmament/events/BakeExtraModelsEvent.kt b/src/main/kotlin/moe/nea/firmament/events/BakeExtraModelsEvent.kt new file mode 100644 index 0000000..7b74166 --- /dev/null +++ b/src/main/kotlin/moe/nea/firmament/events/BakeExtraModelsEvent.kt @@ -0,0 +1,21 @@ +/* + * SPDX-FileCopyrightText: 2024 Linnea Gräf + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +package moe.nea.firmament.events + +import java.util.function.Consumer +import net.minecraft.client.util.ModelIdentifier + +class BakeExtraModelsEvent( + private val addModel: Consumer, +) : FirmamentEvent() { + + fun addModel(modelIdentifier: ModelIdentifier) { + this.addModel.accept(modelIdentifier) + } + + companion object : FirmamentEventBus() +} diff --git a/src/main/kotlin/moe/nea/firmament/events/ClientStartedEvent.kt b/src/main/kotlin/moe/nea/firmament/events/ClientStartedEvent.kt new file mode 100644 index 0000000..151e12d --- /dev/null +++ b/src/main/kotlin/moe/nea/firmament/events/ClientStartedEvent.kt @@ -0,0 +1,11 @@ +/* + * SPDX-FileCopyrightText: 2024 Linnea Gräf + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +package moe.nea.firmament.events + +class ClientStartedEvent : FirmamentEvent() { + companion object : FirmamentEventBus() +} diff --git a/src/main/kotlin/moe/nea/firmament/events/EarlyResourceReloadEvent.kt b/src/main/kotlin/moe/nea/firmament/events/EarlyResourceReloadEvent.kt new file mode 100644 index 0000000..9013aa4 --- /dev/null +++ b/src/main/kotlin/moe/nea/firmament/events/EarlyResourceReloadEvent.kt @@ -0,0 +1,15 @@ +/* + * SPDX-FileCopyrightText: 2024 Linnea Gräf + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +package moe.nea.firmament.events + +import java.util.concurrent.Executor +import net.minecraft.resource.ResourceManager + +data class EarlyResourceReloadEvent(val resourceManager: ResourceManager, val preparationExecutor: Executor) : + FirmamentEvent() { + companion object : FirmamentEventBus() +} diff --git a/src/main/kotlin/moe/nea/firmament/events/FinalizeResourceManagerEvent.kt b/src/main/kotlin/moe/nea/firmament/events/FinalizeResourceManagerEvent.kt new file mode 100644 index 0000000..36b2498 --- /dev/null +++ b/src/main/kotlin/moe/nea/firmament/events/FinalizeResourceManagerEvent.kt @@ -0,0 +1,15 @@ +/* + * SPDX-FileCopyrightText: 2024 Linnea Gräf + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +package moe.nea.firmament.events + +import net.minecraft.resource.ReloadableResourceManagerImpl + +data class FinalizeResourceManagerEvent( + val resourceManager: ReloadableResourceManagerImpl, +) : FirmamentEvent() { + companion object : FirmamentEventBus() +} diff --git a/src/main/kotlin/moe/nea/firmament/features/texturepack/CustomGlobalTextures.kt b/src/main/kotlin/moe/nea/firmament/features/texturepack/CustomGlobalTextures.kt new file mode 100644 index 0000000..2eb4ee1 --- /dev/null +++ b/src/main/kotlin/moe/nea/firmament/features/texturepack/CustomGlobalTextures.kt @@ -0,0 +1,161 @@ +/* + * SPDX-FileCopyrightText: 2024 Linnea Gräf + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +@file:UseSerializers(IdentifierSerializer::class, CustomModelOverrideParser.FirmamentRootPredicateSerializer::class) + +package moe.nea.firmament.features.texturepack + + +import java.util.concurrent.CompletableFuture +import org.slf4j.LoggerFactory +import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable +import kotlinx.serialization.Serializable +import kotlinx.serialization.UseSerializers +import kotlin.jvm.optionals.getOrNull +import net.minecraft.client.render.item.ItemModels +import net.minecraft.client.render.model.BakedModel +import net.minecraft.client.util.ModelIdentifier +import net.minecraft.item.ItemStack +import net.minecraft.resource.ResourceManager +import net.minecraft.resource.SinglePreparationResourceReloader +import net.minecraft.util.Identifier +import net.minecraft.util.profiler.Profiler +import moe.nea.firmament.Firmament +import moe.nea.firmament.annotations.Subscribe +import moe.nea.firmament.events.BakeExtraModelsEvent +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.IdentifierSerializer +import moe.nea.firmament.util.MC +import moe.nea.firmament.util.runNull + +object CustomGlobalTextures : SinglePreparationResourceReloader(), + SubscriptionOwner { + override val delegateFeature: FirmamentFeature + get() = CustomSkyBlockTextures + + class CustomGuiTextureOverride( + val classes: List + ) + + @Serializable + data class GlobalItemOverride( + val screen: Identifier, + val model: Identifier, + val predicate: FirmamentModelPredicate, + ) + + @Serializable + data class ScreenFilter( + val title: StringMatcher, + ) + + data class ItemOverrideCollection( + val screenFilter: ScreenFilter, + val overrides: List, + ) + + @Subscribe + fun onStart(event: FinalizeResourceManagerEvent) { + MC.resourceManager.registerReloader(this) + } + + @Subscribe + fun onEarlyReload(event: EarlyResourceReloadEvent) { + preparationFuture = CompletableFuture + .supplyAsync( + { + prepare(event.resourceManager) + }, event.preparationExecutor) + } + + @Subscribe + fun onBakeModels(event: BakeExtraModelsEvent) { + for (guiClassOverride in preparationFuture.join().classes) { + for (override in guiClassOverride.overrides) { + event.addModel(ModelIdentifier(override.model, "inventory")) + } + } + } + + @Volatile + var preparationFuture: CompletableFuture = CompletableFuture.completedFuture( + CustomGuiTextureOverride(listOf())) + + override fun prepare(manager: ResourceManager?, profiler: Profiler?): CustomGuiTextureOverride { + return preparationFuture.join() + } + + override fun apply(prepared: CustomGuiTextureOverride, manager: ResourceManager?, profiler: Profiler?) { + this.guiClassOverrides = prepared + } + + val logger = LoggerFactory.getLogger(CustomGlobalTextures::class.java) + fun prepare(manager: ResourceManager): CustomGuiTextureOverride { + val overrideResources = + manager.findResources("overrides/item") { it.namespace == "firmskyblock" && it.path.endsWith(".json") } + .mapNotNull { + Firmament.tryDecodeJsonFromStream(it.value.inputStream).getOrElse { ex -> + logger.error("Failed to load global item override at ${it.key}", ex) + null + } + } + + val byGuiClass = overrideResources.groupBy { it.screen } + val guiClasses = byGuiClass.entries + .mapNotNull { + val key = it.key + val guiClassResource = + manager.getResource(Identifier(key.namespace, "filters/screen/${key.path}.json")) + .getOrNull() + ?: return@mapNotNull runNull { + logger.error("Failed to locate screen filter at $key") + } + val screenFilter = + Firmament.tryDecodeJsonFromStream(guiClassResource.inputStream) + .getOrElse { ex -> + logger.error("Failed to load screen filter at $key", ex) + return@mapNotNull null + } + ItemOverrideCollection(screenFilter, it.value) + } + logger.info("Loaded ${overrideResources.size} global item overrides") + return CustomGuiTextureOverride(guiClasses) + } + + var guiClassOverrides = CustomGuiTextureOverride(listOf()) + + var matchingOverrides: List = listOf() + + @Subscribe + fun onOpenGui(event: ScreenChangeEvent) { + val newTitle = event.new?.title + matchingOverrides = + if (newTitle == null) listOf() + else guiClassOverrides.classes.filter { it.screenFilter.title.matches(newTitle) } + } + + @JvmStatic + fun replaceGlobalModel( + models: ItemModels, + stack: ItemStack, + cir: CallbackInfoReturnable + ) { + for (guiClassOverride in matchingOverrides) { + for (override in guiClassOverride.overrides) { + if (override.predicate.test(stack)) { + cir.returnValue = models.modelManager.getModel(ModelIdentifier(override.model, "inventory")) + return + } + } + } + } + + +} diff --git a/src/main/kotlin/moe/nea/firmament/features/texturepack/CustomModelOverrideParser.kt b/src/main/kotlin/moe/nea/firmament/features/texturepack/CustomModelOverrideParser.kt index ad1f436..d512dec 100644 --- a/src/main/kotlin/moe/nea/firmament/features/texturepack/CustomModelOverrideParser.kt +++ b/src/main/kotlin/moe/nea/firmament/features/texturepack/CustomModelOverrideParser.kt @@ -7,9 +7,27 @@ package moe.nea.firmament.features.texturepack import com.google.gson.JsonObject +import kotlinx.serialization.KSerializer +import kotlinx.serialization.descriptors.SerialDescriptor +import kotlinx.serialization.encoding.Decoder +import kotlinx.serialization.encoding.Encoder import net.minecraft.util.Identifier object CustomModelOverrideParser { + object FirmamentRootPredicateSerializer : KSerializer { + val delegateSerializer = kotlinx.serialization.json.JsonObject.serializer() + override val descriptor: SerialDescriptor + get() = SerialDescriptor("FirmamentModelRootPredicate", delegateSerializer.descriptor) + + override fun deserialize(decoder: Decoder): FirmamentModelPredicate { + val json = decoder.decodeSerializableValue(delegateSerializer).intoGson() as JsonObject + return AndPredicate(parsePredicates(json).toTypedArray()) + } + + override fun serialize(encoder: Encoder, value: FirmamentModelPredicate) { + TODO("Cannot serialize firmament predicates") + } + } val predicateParsers = mutableMapOf() diff --git a/src/main/kotlin/moe/nea/firmament/features/texturepack/CustomSkyBlockTextures.kt b/src/main/kotlin/moe/nea/firmament/features/texturepack/CustomSkyBlockTextures.kt index e66a24a..7c2d5ab 100644 --- a/src/main/kotlin/moe/nea/firmament/features/texturepack/CustomSkyBlockTextures.kt +++ b/src/main/kotlin/moe/nea/firmament/features/texturepack/CustomSkyBlockTextures.kt @@ -17,6 +17,7 @@ import net.minecraft.client.util.ModelIdentifier import net.minecraft.component.type.ProfileComponent import net.minecraft.util.Identifier import moe.nea.firmament.annotations.Subscribe +import moe.nea.firmament.events.BakeExtraModelsEvent import moe.nea.firmament.events.CustomItemModelEvent import moe.nea.firmament.events.TickEvent import moe.nea.firmament.features.FirmamentFeature @@ -47,6 +48,23 @@ object CustomSkyBlockTextures : FirmamentFeature { } } + @Subscribe + fun bakeCustomFirmModels(event: BakeExtraModelsEvent) { + val resources = + MinecraftClient.getInstance().resourceManager.findResources("models/item" + ) { it: Identifier -> + "firmskyblock" == it.namespace && it.path + .endsWith(".json") + } + for (identifier in resources.keys) { + val modelId = ModelIdentifier("firmskyblock", + identifier.path.substring("models/item/".length, + identifier.path.length - ".json".length), + "inventory") + event.addModel(modelId) + } + } + @Subscribe fun onCustomModelId(it: CustomItemModelEvent) { if (!TConfig.enabled) return diff --git a/src/main/kotlin/moe/nea/firmament/features/texturepack/StringMatcher.kt b/src/main/kotlin/moe/nea/firmament/features/texturepack/StringMatcher.kt index 8c1ccbc..1c2675f 100644 --- a/src/main/kotlin/moe/nea/firmament/features/texturepack/StringMatcher.kt +++ b/src/main/kotlin/moe/nea/firmament/features/texturepack/StringMatcher.kt @@ -6,15 +6,24 @@ package moe.nea.firmament.features.texturepack +import com.google.gson.JsonArray import com.google.gson.JsonElement +import com.google.gson.JsonNull import com.google.gson.JsonObject import com.google.gson.JsonPrimitive +import com.google.gson.internal.LazilyParsedNumber import java.util.function.Predicate +import kotlinx.serialization.KSerializer +import kotlinx.serialization.Serializable +import kotlinx.serialization.descriptors.SerialDescriptor +import kotlinx.serialization.encoding.Decoder +import kotlinx.serialization.encoding.Encoder import net.minecraft.nbt.NbtString import net.minecraft.text.Text import moe.nea.firmament.util.MC import moe.nea.firmament.util.removeColorCodes +@Serializable(with = StringMatcher.Serializer::class) interface StringMatcher { fun matches(string: String): Boolean fun matches(text: Text): Boolean { @@ -46,7 +55,28 @@ interface StringMatcher { } } + object Serializer : KSerializer { + 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, Companion.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) @@ -69,3 +99,58 @@ interface StringMatcher { } } } + +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 + } + } +} diff --git a/src/main/kotlin/moe/nea/firmament/util/IdentifierSerializer.kt b/src/main/kotlin/moe/nea/firmament/util/IdentifierSerializer.kt new file mode 100644 index 0000000..3c1aa52 --- /dev/null +++ b/src/main/kotlin/moe/nea/firmament/util/IdentifierSerializer.kt @@ -0,0 +1,28 @@ +/* + * SPDX-FileCopyrightText: 2024 Linnea Gräf + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +package moe.nea.firmament.util + +import kotlinx.serialization.KSerializer +import kotlinx.serialization.builtins.serializer +import kotlinx.serialization.descriptors.SerialDescriptor +import kotlinx.serialization.encoding.Decoder +import kotlinx.serialization.encoding.Encoder +import net.minecraft.util.Identifier + +object IdentifierSerializer : KSerializer { + val delegateSerializer = String.serializer() + override val descriptor: SerialDescriptor + get() = SerialDescriptor("Identifier", delegateSerializer.descriptor) + + override fun deserialize(decoder: Decoder): Identifier { + return Identifier(decoder.decodeSerializableValue(delegateSerializer)) + } + + override fun serialize(encoder: Encoder, value: Identifier) { + encoder.encodeSerializableValue(delegateSerializer, value.toString()) + } +} diff --git a/src/main/kotlin/moe/nea/firmament/util/LogIfNull.kt b/src/main/kotlin/moe/nea/firmament/util/LogIfNull.kt new file mode 100644 index 0000000..b0476ee --- /dev/null +++ b/src/main/kotlin/moe/nea/firmament/util/LogIfNull.kt @@ -0,0 +1,13 @@ +/* + * SPDX-FileCopyrightText: 2024 Linnea Gräf + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +package moe.nea.firmament.util + + +fun runNull(block: () -> Unit): Nothing? { + block() + return null +} diff --git a/src/main/resources/assets/firmament/filters/screen/always.json b/src/main/resources/assets/firmament/filters/screen/always.json new file mode 100644 index 0000000..6c21cc9 --- /dev/null +++ b/src/main/resources/assets/firmament/filters/screen/always.json @@ -0,0 +1,5 @@ +{ + "title": { + "regex": ".*" + } +} diff --git a/src/main/resources/assets/firmament/filters/screen/always.json.license b/src/main/resources/assets/firmament/filters/screen/always.json.license new file mode 100644 index 0000000..5f0659f --- /dev/null +++ b/src/main/resources/assets/firmament/filters/screen/always.json.license @@ -0,0 +1,3 @@ +SPDX-FileCopyrightText: 2024 Linnea Gräf + +SPDX-License-Identifier: CC0-1.0 diff --git a/src/main/resources/assets/firmament/icon.png b/src/main/resources/assets/firmament/icon.png deleted file mode 100644 index 467f962..0000000 Binary files a/src/main/resources/assets/firmament/icon.png and /dev/null differ diff --git a/src/main/resources/assets/firmament/icon.png.license b/src/main/resources/assets/firmament/icon.png.license deleted file mode 100644 index f168dcf..0000000 --- a/src/main/resources/assets/firmament/icon.png.license +++ /dev/null @@ -1,3 +0,0 @@ -SPDX-FileCopyrightText: 2023 Linnea Gräf - -SPDX-License-Identifier: CC-BY-4.0 -- cgit