Add custom global textures

This commit is contained in:
Linnea Gräf
2024-05-11 03:28:05 +02:00
parent 53dc0c3b0a
commit 7682534f6f
20 changed files with 556 additions and 16 deletions

View File

@@ -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 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. 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/<namespace>/models/item/<id>.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/<namespace>/filters/screen/<id>.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).

View File

@@ -1,11 +1,13 @@
/* /*
* SPDX-FileCopyrightText: 2023 Linnea Gräf <nea@nea.moe> * SPDX-FileCopyrightText: 2023 Linnea Gräf <nea@nea.moe>
* SPDX-FileCopyrightText: 2024 Linnea Gräf <nea@nea.moe>
* *
* SPDX-License-Identifier: GPL-3.0-or-later * SPDX-License-Identifier: GPL-3.0-or-later
*/ */
package moe.nea.firmament.mixins; package moe.nea.firmament.mixins;
import moe.nea.firmament.events.BakeExtraModelsEvent;
import net.minecraft.client.MinecraftClient; import net.minecraft.client.MinecraftClient;
import net.minecraft.client.render.model.ModelLoader; import net.minecraft.client.render.model.ModelLoader;
import net.minecraft.client.render.model.UnbakedModel; import net.minecraft.client.render.model.UnbakedModel;
@@ -39,12 +41,7 @@ public abstract class CustomModelBakerPatch {
@Inject(method = "bake", at = @At("HEAD")) @Inject(method = "bake", at = @At("HEAD"))
public void onBake(BiFunction<Identifier, SpriteIdentifier, Sprite> spriteLoader, CallbackInfo ci) { public void onBake(BiFunction<Identifier, SpriteIdentifier, Sprite> spriteLoader, CallbackInfo ci) {
Map<Identifier, Resource> resources = BakeExtraModelsEvent.Companion.publish(new BakeExtraModelsEvent(this::addModel));
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);
}
modelsToBake.values().forEach(model -> model.setParents(this::getOrLoadModel)); modelsToBake.values().forEach(model -> model.setParents(this::getOrLoadModel));
} }
} }

View File

@@ -0,0 +1,30 @@
/*
* SPDX-FileCopyrightText: 2024 Linnea Gräf <nea@nea.moe>
*
* 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<Unit> initialStage, List<ResourcePack> packs, CallbackInfoReturnable<ResourceReload> cir) {
EarlyResourceReloadEvent.Companion.publish(new EarlyResourceReloadEvent(this, prepareExecutor));
}
}

View File

@@ -0,0 +1,31 @@
/*
* SPDX-FileCopyrightText: 2024 Linnea Gräf <nea@nea.moe>
*
* 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 = "<init>", 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));
}
}

View File

@@ -0,0 +1,34 @@
/*
* SPDX-FileCopyrightText: 2024 Linnea Gräf <nea@nea.moe>
*
* 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<BakedModel> cir) {
CustomGlobalTextures.replaceGlobalModel(this.getModels(), stack, cir);
}
}

View File

@@ -8,13 +8,15 @@
package moe.nea.firmament package moe.nea.firmament
import com.mojang.brigadier.CommandDispatcher import com.mojang.brigadier.CommandDispatcher
import io.ktor.client.* import io.ktor.client.HttpClient
import io.ktor.client.plugins.* import io.ktor.client.plugins.UserAgent
import io.ktor.client.plugins.cache.* import io.ktor.client.plugins.cache.HttpCache
import io.ktor.client.plugins.compression.* import io.ktor.client.plugins.compression.ContentEncoding
import io.ktor.client.plugins.contentnegotiation.* import io.ktor.client.plugins.contentnegotiation.ContentNegotiation
import io.ktor.client.plugins.logging.* import io.ktor.client.plugins.logging.LogLevel
import io.ktor.serialization.kotlinx.json.* 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.Files
import java.nio.file.Path import java.nio.file.Path
import net.fabricmc.fabric.api.client.command.v2.ClientCommandRegistrationCallback import net.fabricmc.fabric.api.client.command.v2.ClientCommandRegistrationCallback
@@ -36,11 +38,13 @@ import kotlinx.coroutines.Job
import kotlinx.coroutines.SupervisorJob import kotlinx.coroutines.SupervisorJob
import kotlinx.coroutines.plus import kotlinx.coroutines.plus
import kotlinx.serialization.json.Json import kotlinx.serialization.json.Json
import kotlinx.serialization.json.decodeFromStream
import kotlin.coroutines.EmptyCoroutineContext import kotlin.coroutines.EmptyCoroutineContext
import net.minecraft.command.CommandRegistryAccess import net.minecraft.command.CommandRegistryAccess
import net.minecraft.util.Identifier import net.minecraft.util.Identifier
import moe.nea.firmament.commands.registerFirmamentCommand import moe.nea.firmament.commands.registerFirmamentCommand
import moe.nea.firmament.dbus.FirmamentDbusObject import moe.nea.firmament.dbus.FirmamentDbusObject
import moe.nea.firmament.events.ClientStartedEvent
import moe.nea.firmament.events.CommandEvent import moe.nea.firmament.events.CommandEvent
import moe.nea.firmament.events.ItemTooltipEvent import moe.nea.firmament.events.ItemTooltipEvent
import moe.nea.firmament.events.ScreenRenderPostEvent import moe.nea.firmament.events.ScreenRenderPostEvent
@@ -133,6 +137,9 @@ object Firmament {
FeatureManager.autoload() FeatureManager.autoload()
HypixelStaticData.spawnDataCollectionLoop() HypixelStaticData.spawnDataCollectionLoop()
ClientCommandRegistrationCallback.EVENT.register(this::registerCommands) ClientCommandRegistrationCallback.EVENT.register(this::registerCommands)
ClientLifecycleEvents.CLIENT_STARTED.register(ClientLifecycleEvents.ClientStarted {
ClientStartedEvent.publish(ClientStartedEvent())
})
ClientLifecycleEvents.CLIENT_STOPPING.register(ClientLifecycleEvents.ClientStopping { ClientLifecycleEvents.CLIENT_STOPPING.register(ClientLifecycleEvents.ClientStopping {
logger.info("Shutting down Firmament coroutines") logger.info("Shutting down Firmament coroutines")
globalJob.cancel() globalJob.cancel()
@@ -151,4 +158,9 @@ object Firmament {
fun identifier(path: String) = Identifier(MOD_ID, path) fun identifier(path: String) = Identifier(MOD_ID, path)
inline fun <reified T : Any> tryDecodeJsonFromStream(inputStream: InputStream): Result<T> {
return runCatching {
json.decodeFromStream<T>(inputStream)
}
}
} }

View File

@@ -0,0 +1,21 @@
/*
* SPDX-FileCopyrightText: 2024 Linnea Gräf <nea@nea.moe>
*
* 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<ModelIdentifier>,
) : FirmamentEvent() {
fun addModel(modelIdentifier: ModelIdentifier) {
this.addModel.accept(modelIdentifier)
}
companion object : FirmamentEventBus<BakeExtraModelsEvent>()
}

View File

@@ -0,0 +1,11 @@
/*
* SPDX-FileCopyrightText: 2024 Linnea Gräf <nea@nea.moe>
*
* SPDX-License-Identifier: GPL-3.0-or-later
*/
package moe.nea.firmament.events
class ClientStartedEvent : FirmamentEvent() {
companion object : FirmamentEventBus<ClientStartedEvent>()
}

View File

@@ -0,0 +1,15 @@
/*
* SPDX-FileCopyrightText: 2024 Linnea Gräf <nea@nea.moe>
*
* 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<EarlyResourceReloadEvent>()
}

View File

@@ -0,0 +1,15 @@
/*
* SPDX-FileCopyrightText: 2024 Linnea Gräf <nea@nea.moe>
*
* 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<FinalizeResourceManagerEvent>()
}

View File

@@ -0,0 +1,161 @@
/*
* SPDX-FileCopyrightText: 2024 Linnea Gräf <nea@nea.moe>
*
* 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<CustomGlobalTextures.CustomGuiTextureOverride>(),
SubscriptionOwner {
override val delegateFeature: FirmamentFeature
get() = CustomSkyBlockTextures
class CustomGuiTextureOverride(
val classes: List<ItemOverrideCollection>
)
@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<GlobalItemOverride>,
)
@Subscribe
fun onStart(event: FinalizeResourceManagerEvent) {
MC.resourceManager.registerReloader(this)
}
@Subscribe
fun onEarlyReload(event: EarlyResourceReloadEvent) {
preparationFuture = CompletableFuture
.supplyAsync(
{
prepare(event.resourceManager)
}, event.preparationExecutor)
}
@Subscribe
fun onBakeModels(event: BakeExtraModelsEvent) {
for (guiClassOverride in preparationFuture.join().classes) {
for (override in guiClassOverride.overrides) {
event.addModel(ModelIdentifier(override.model, "inventory"))
}
}
}
@Volatile
var preparationFuture: CompletableFuture<CustomGuiTextureOverride> = CompletableFuture.completedFuture(
CustomGuiTextureOverride(listOf()))
override fun prepare(manager: ResourceManager?, profiler: Profiler?): CustomGuiTextureOverride {
return preparationFuture.join()
}
override fun apply(prepared: CustomGuiTextureOverride, manager: ResourceManager?, profiler: Profiler?) {
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<GlobalItemOverride>(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<ScreenFilter>(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<ItemOverrideCollection> = 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<BakedModel>
) {
for (guiClassOverride in matchingOverrides) {
for (override in guiClassOverride.overrides) {
if (override.predicate.test(stack)) {
cir.returnValue = models.modelManager.getModel(ModelIdentifier(override.model, "inventory"))
return
}
}
}
}
}

View File

@@ -7,9 +7,27 @@
package moe.nea.firmament.features.texturepack package moe.nea.firmament.features.texturepack
import com.google.gson.JsonObject 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 import net.minecraft.util.Identifier
object CustomModelOverrideParser { object CustomModelOverrideParser {
object FirmamentRootPredicateSerializer : KSerializer<FirmamentModelPredicate> {
val delegateSerializer = kotlinx.serialization.json.JsonObject.serializer()
override val descriptor: SerialDescriptor
get() = SerialDescriptor("FirmamentModelRootPredicate", delegateSerializer.descriptor)
override fun deserialize(decoder: Decoder): FirmamentModelPredicate {
val json = decoder.decodeSerializableValue(delegateSerializer).intoGson() as JsonObject
return AndPredicate(parsePredicates(json).toTypedArray())
}
override fun serialize(encoder: Encoder, value: FirmamentModelPredicate) {
TODO("Cannot serialize firmament predicates")
}
}
val predicateParsers = mutableMapOf<Identifier, FirmamentModelPredicateParser>() val predicateParsers = mutableMapOf<Identifier, FirmamentModelPredicateParser>()

View File

@@ -17,6 +17,7 @@ import net.minecraft.client.util.ModelIdentifier
import net.minecraft.component.type.ProfileComponent import net.minecraft.component.type.ProfileComponent
import net.minecraft.util.Identifier import net.minecraft.util.Identifier
import moe.nea.firmament.annotations.Subscribe import moe.nea.firmament.annotations.Subscribe
import moe.nea.firmament.events.BakeExtraModelsEvent
import moe.nea.firmament.events.CustomItemModelEvent import moe.nea.firmament.events.CustomItemModelEvent
import moe.nea.firmament.events.TickEvent import moe.nea.firmament.events.TickEvent
import moe.nea.firmament.features.FirmamentFeature 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 @Subscribe
fun onCustomModelId(it: CustomItemModelEvent) { fun onCustomModelId(it: CustomItemModelEvent) {
if (!TConfig.enabled) return if (!TConfig.enabled) return

View File

@@ -6,15 +6,24 @@
package moe.nea.firmament.features.texturepack package moe.nea.firmament.features.texturepack
import com.google.gson.JsonArray
import com.google.gson.JsonElement import com.google.gson.JsonElement
import com.google.gson.JsonNull
import com.google.gson.JsonObject import com.google.gson.JsonObject
import com.google.gson.JsonPrimitive import com.google.gson.JsonPrimitive
import com.google.gson.internal.LazilyParsedNumber
import java.util.function.Predicate 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.nbt.NbtString
import net.minecraft.text.Text import net.minecraft.text.Text
import moe.nea.firmament.util.MC import moe.nea.firmament.util.MC
import moe.nea.firmament.util.removeColorCodes import moe.nea.firmament.util.removeColorCodes
@Serializable(with = StringMatcher.Serializer::class)
interface StringMatcher { interface StringMatcher {
fun matches(string: String): Boolean fun matches(string: String): Boolean
fun matches(text: Text): Boolean { fun matches(text: Text): Boolean {
@@ -46,7 +55,28 @@ interface StringMatcher {
} }
} }
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, Companion.serialize(value).intoKotlinJson())
}
}
companion object { companion object {
fun serialize(stringMatcher: StringMatcher):JsonElement {
TODO("Cannot serialize string matchers rn")
}
fun parse(jsonElement: JsonElement): StringMatcher { fun parse(jsonElement: JsonElement): StringMatcher {
if (jsonElement is JsonPrimitive) { if (jsonElement is JsonPrimitive) {
return Equals(jsonElement.asString, true) 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
}
}
}

View File

@@ -0,0 +1,28 @@
/*
* SPDX-FileCopyrightText: 2024 Linnea Gräf <nea@nea.moe>
*
* 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<Identifier> {
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())
}
}

View File

@@ -0,0 +1,13 @@
/*
* SPDX-FileCopyrightText: 2024 Linnea Gräf <nea@nea.moe>
*
* SPDX-License-Identifier: GPL-3.0-or-later
*/
package moe.nea.firmament.util
fun runNull(block: () -> Unit): Nothing? {
block()
return null
}

View File

@@ -0,0 +1,5 @@
{
"title": {
"regex": ".*"
}
}

View File

@@ -0,0 +1,3 @@
SPDX-FileCopyrightText: 2024 Linnea Gräf <nea@nea.moe>
SPDX-License-Identifier: CC0-1.0

Binary file not shown.

Before

Width:  |  Height:  |  Size: 20 KiB

View File

@@ -1,3 +0,0 @@
SPDX-FileCopyrightText: 2023 Linnea Gräf <nea@nea.moe>
SPDX-License-Identifier: CC-BY-4.0