From 602112724d8236c1ec6671e1893128862c9f5815 Mon Sep 17 00:00:00 2001 From: Linnea Gräf Date: Thu, 22 Feb 2024 00:30:05 +0100 Subject: Add custom model predicates Add regex support Add and and or predicates --- .../firmament/events/FeaturesInitializedEvent.kt | 13 ++++ .../moe/nea/firmament/features/FeatureManager.kt | 3 + .../features/notifications/Notifications.kt | 12 ++++ .../firmament/features/texturepack/AndPredicate.kt | 31 ++++++++++ .../features/texturepack/BakedOverrideData.kt | 13 ++++ .../texturepack/CustomModelOverrideParser.kt | 48 +++++++++++++++ .../features/texturepack/CustomSkyBlockTextures.kt | 2 +- .../features/texturepack/DisplayNamePredicate.kt | 28 +++++++++ .../texturepack/FirmamentModelPredicate.kt | 13 ++++ .../texturepack/FirmamentModelPredicateParser.kt | 13 ++++ .../features/texturepack/LorePredicate.kt | 28 +++++++++ .../features/texturepack/ModelOverrideData.kt | 12 ++++ .../features/texturepack/ModelOverrideFilterSet.kt | 24 ++++++++ .../firmament/features/texturepack/OrPredicate.kt | 31 ++++++++++ .../features/texturepack/StringMatcher.kt | 70 ++++++++++++++++++++++ .../nea/firmament/util/filter/IteratorFilterSet.kt | 38 ++++++++++++ src/main/kotlin/moe/nea/firmament/util/textutil.kt | 26 +++++++- 17 files changed, 402 insertions(+), 3 deletions(-) create mode 100644 src/main/kotlin/moe/nea/firmament/events/FeaturesInitializedEvent.kt create mode 100644 src/main/kotlin/moe/nea/firmament/features/notifications/Notifications.kt create mode 100644 src/main/kotlin/moe/nea/firmament/features/texturepack/AndPredicate.kt create mode 100644 src/main/kotlin/moe/nea/firmament/features/texturepack/BakedOverrideData.kt create mode 100644 src/main/kotlin/moe/nea/firmament/features/texturepack/CustomModelOverrideParser.kt create mode 100644 src/main/kotlin/moe/nea/firmament/features/texturepack/DisplayNamePredicate.kt create mode 100644 src/main/kotlin/moe/nea/firmament/features/texturepack/FirmamentModelPredicate.kt create mode 100644 src/main/kotlin/moe/nea/firmament/features/texturepack/FirmamentModelPredicateParser.kt create mode 100644 src/main/kotlin/moe/nea/firmament/features/texturepack/LorePredicate.kt create mode 100644 src/main/kotlin/moe/nea/firmament/features/texturepack/ModelOverrideData.kt create mode 100644 src/main/kotlin/moe/nea/firmament/features/texturepack/ModelOverrideFilterSet.kt create mode 100644 src/main/kotlin/moe/nea/firmament/features/texturepack/OrPredicate.kt create mode 100644 src/main/kotlin/moe/nea/firmament/features/texturepack/StringMatcher.kt create mode 100644 src/main/kotlin/moe/nea/firmament/util/filter/IteratorFilterSet.kt (limited to 'src/main/kotlin') diff --git a/src/main/kotlin/moe/nea/firmament/events/FeaturesInitializedEvent.kt b/src/main/kotlin/moe/nea/firmament/events/FeaturesInitializedEvent.kt new file mode 100644 index 0000000..da18568 --- /dev/null +++ b/src/main/kotlin/moe/nea/firmament/events/FeaturesInitializedEvent.kt @@ -0,0 +1,13 @@ +/* + * SPDX-FileCopyrightText: 2024 Linnea Gräf + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +package moe.nea.firmament.events + +import moe.nea.firmament.features.FirmamentFeature + +data class FeaturesInitializedEvent(val features: List) : FirmamentEvent() { + companion object : FirmamentEventBus() +} diff --git a/src/main/kotlin/moe/nea/firmament/features/FeatureManager.kt b/src/main/kotlin/moe/nea/firmament/features/FeatureManager.kt index 99f84e6..5e7f612 100644 --- a/src/main/kotlin/moe/nea/firmament/features/FeatureManager.kt +++ b/src/main/kotlin/moe/nea/firmament/features/FeatureManager.kt @@ -1,5 +1,6 @@ /* * SPDX-FileCopyrightText: 2023 Linnea Gräf + * SPDX-FileCopyrightText: 2024 Linnea Gräf * * SPDX-License-Identifier: GPL-3.0-or-later */ @@ -9,6 +10,7 @@ package moe.nea.firmament.features import kotlinx.serialization.Serializable import kotlinx.serialization.serializer import moe.nea.firmament.Firmament +import moe.nea.firmament.events.FeaturesInitializedEvent import moe.nea.firmament.features.chat.AutoCompletions import moe.nea.firmament.features.chat.ChatLinks import moe.nea.firmament.features.chat.QuickCommands @@ -76,6 +78,7 @@ object FeatureManager : DataHolder(serializer(), "feature loadFeature(DebugView) } allFeatures.forEach { it.config } + FeaturesInitializedEvent.publish(FeaturesInitializedEvent(allFeatures.toList())) hasAutoloaded = true } } diff --git a/src/main/kotlin/moe/nea/firmament/features/notifications/Notifications.kt b/src/main/kotlin/moe/nea/firmament/features/notifications/Notifications.kt new file mode 100644 index 0000000..1f3a572 --- /dev/null +++ b/src/main/kotlin/moe/nea/firmament/features/notifications/Notifications.kt @@ -0,0 +1,12 @@ +/* + * SPDX-FileCopyrightText: 2024 Linnea Gräf + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +package moe.nea.firmament.features.notifications + +import moe.nea.firmament.features.FirmamentFeature + +object Notifications { +} diff --git a/src/main/kotlin/moe/nea/firmament/features/texturepack/AndPredicate.kt b/src/main/kotlin/moe/nea/firmament/features/texturepack/AndPredicate.kt new file mode 100644 index 0000000..5ad023e --- /dev/null +++ b/src/main/kotlin/moe/nea/firmament/features/texturepack/AndPredicate.kt @@ -0,0 +1,31 @@ +/* + * SPDX-FileCopyrightText: 2024 Linnea Gräf + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +package moe.nea.firmament.features.texturepack + +import com.google.gson.JsonArray +import com.google.gson.JsonElement +import com.google.gson.JsonObject +import net.minecraft.item.ItemStack + +class AndPredicate(val children: Array) : FirmamentModelPredicate { + override fun test(stack: ItemStack): Boolean { + return children.all { it.test(stack) } + } + + object Parser : FirmamentModelPredicateParser { + override fun parse(jsonElement: JsonElement): FirmamentModelPredicate { + val children = + (jsonElement as JsonArray) + .flatMap { + CustomModelOverrideParser.parsePredicates(it as JsonObject) + } + .toTypedArray() + return AndPredicate(children) + } + + } +} diff --git a/src/main/kotlin/moe/nea/firmament/features/texturepack/BakedOverrideData.kt b/src/main/kotlin/moe/nea/firmament/features/texturepack/BakedOverrideData.kt new file mode 100644 index 0000000..8ed8402 --- /dev/null +++ b/src/main/kotlin/moe/nea/firmament/features/texturepack/BakedOverrideData.kt @@ -0,0 +1,13 @@ +/* + * SPDX-FileCopyrightText: 2024 Linnea Gräf + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +package moe.nea.firmament.features.texturepack + +interface BakedOverrideData { + fun getFirmamentOverrides(): Array? + fun setFirmamentOverrides(overrides: Array?) + +} diff --git a/src/main/kotlin/moe/nea/firmament/features/texturepack/CustomModelOverrideParser.kt b/src/main/kotlin/moe/nea/firmament/features/texturepack/CustomModelOverrideParser.kt new file mode 100644 index 0000000..ac62eaa --- /dev/null +++ b/src/main/kotlin/moe/nea/firmament/features/texturepack/CustomModelOverrideParser.kt @@ -0,0 +1,48 @@ +/* + * SPDX-FileCopyrightText: 2024 Linnea Gräf + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +package moe.nea.firmament.features.texturepack + +import com.google.gson.JsonObject +import net.minecraft.util.Identifier + +object CustomModelOverrideParser { + + val predicateParsers = mutableMapOf() + + + fun registerPredicateParser(name: String, parser: FirmamentModelPredicateParser) { + predicateParsers[Identifier("firmament", name)] = parser + } + + init { + registerPredicateParser("display_name", DisplayNamePredicate.Parser) + registerPredicateParser("lore", LorePredicate.Parser) + registerPredicateParser("all", AndPredicate.Parser) + registerPredicateParser("any", OrPredicate.Parser) + } + + fun parsePredicates(predicates: JsonObject): List { + val parsedPredicates = mutableListOf() + for (predicateName in predicates.keySet()) { + if (!predicateName.startsWith("firmament:")) continue + val identifier = Identifier(predicateName) + val parser = predicateParsers[identifier] ?: continue + val parsedPredicate = parser.parse(predicates[predicateName]) + parsedPredicates.add(parsedPredicate) + } + return parsedPredicates + } + + @JvmStatic + fun parseCustomModelOverrides(jsonObject: JsonObject): Array? { + val predicates = (jsonObject["predicate"] as? JsonObject) ?: return null + val parsedPredicates = parsePredicates(predicates) + if (parsedPredicates.isEmpty()) + return null + return parsedPredicates.toTypedArray() + } +} 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 64dec99..66c0036 100644 --- a/src/main/kotlin/moe/nea/firmament/features/texturepack/CustomSkyBlockTextures.kt +++ b/src/main/kotlin/moe/nea/firmament/features/texturepack/CustomSkyBlockTextures.kt @@ -13,7 +13,6 @@ import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable import net.minecraft.block.SkullBlock import net.minecraft.client.MinecraftClient import net.minecraft.client.render.RenderLayer -import net.minecraft.client.texture.PlayerSkinProvider import net.minecraft.client.util.ModelIdentifier import net.minecraft.util.Identifier import moe.nea.firmament.events.CustomItemModelEvent @@ -33,6 +32,7 @@ object CustomSkyBlockTextures : FirmamentFeature { val enabled by toggle("enabled") { true } val skullsEnabled by toggle("skulls-enabled") { true } val cacheDuration by integer("cache-duration", 0, 20) { 1 } + val enableModelOverrides by toggle("model-overrides") { true } } override val config: ManagedConfig diff --git a/src/main/kotlin/moe/nea/firmament/features/texturepack/DisplayNamePredicate.kt b/src/main/kotlin/moe/nea/firmament/features/texturepack/DisplayNamePredicate.kt new file mode 100644 index 0000000..373910a --- /dev/null +++ b/src/main/kotlin/moe/nea/firmament/features/texturepack/DisplayNamePredicate.kt @@ -0,0 +1,28 @@ +/* + * SPDX-FileCopyrightText: 2024 Linnea Gräf + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +package moe.nea.firmament.features.texturepack + +import com.google.gson.JsonElement +import net.minecraft.item.ItemStack +import net.minecraft.nbt.NbtElement +import net.minecraft.nbt.NbtString + +data class DisplayNamePredicate(val stringMatcher: StringMatcher) : FirmamentModelPredicate { + override fun test(stack: ItemStack): Boolean { + val display = stack.getOrCreateSubNbt(ItemStack.DISPLAY_KEY) + return if (display.contains(ItemStack.NAME_KEY, NbtElement.STRING_TYPE.toInt())) + stringMatcher.matches(display.get(ItemStack.NAME_KEY) as NbtString) + else + false + } + + object Parser : FirmamentModelPredicateParser { + override fun parse(jsonElement: JsonElement): FirmamentModelPredicate { + return DisplayNamePredicate(StringMatcher.parse(jsonElement)) + } + } +} diff --git a/src/main/kotlin/moe/nea/firmament/features/texturepack/FirmamentModelPredicate.kt b/src/main/kotlin/moe/nea/firmament/features/texturepack/FirmamentModelPredicate.kt new file mode 100644 index 0000000..8dcdaf3 --- /dev/null +++ b/src/main/kotlin/moe/nea/firmament/features/texturepack/FirmamentModelPredicate.kt @@ -0,0 +1,13 @@ +/* + * SPDX-FileCopyrightText: 2024 Linnea Gräf + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +package moe.nea.firmament.features.texturepack + +import net.minecraft.item.ItemStack + +interface FirmamentModelPredicate { + fun test(stack: ItemStack): Boolean +} diff --git a/src/main/kotlin/moe/nea/firmament/features/texturepack/FirmamentModelPredicateParser.kt b/src/main/kotlin/moe/nea/firmament/features/texturepack/FirmamentModelPredicateParser.kt new file mode 100644 index 0000000..de7557a --- /dev/null +++ b/src/main/kotlin/moe/nea/firmament/features/texturepack/FirmamentModelPredicateParser.kt @@ -0,0 +1,13 @@ +/* + * SPDX-FileCopyrightText: 2024 Linnea Gräf + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +package moe.nea.firmament.features.texturepack + +import com.google.gson.JsonElement + +interface FirmamentModelPredicateParser { + fun parse(jsonElement: JsonElement): FirmamentModelPredicate +} diff --git a/src/main/kotlin/moe/nea/firmament/features/texturepack/LorePredicate.kt b/src/main/kotlin/moe/nea/firmament/features/texturepack/LorePredicate.kt new file mode 100644 index 0000000..604d29c --- /dev/null +++ b/src/main/kotlin/moe/nea/firmament/features/texturepack/LorePredicate.kt @@ -0,0 +1,28 @@ +/* + * SPDX-FileCopyrightText: 2024 Linnea Gräf + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +package moe.nea.firmament.features.texturepack + +import com.google.gson.JsonElement +import net.minecraft.item.ItemStack +import net.minecraft.nbt.NbtElement +import net.minecraft.nbt.NbtString + +class LorePredicate(val matcher: StringMatcher) : FirmamentModelPredicate { + object Parser : FirmamentModelPredicateParser { + override fun parse(jsonElement: JsonElement): FirmamentModelPredicate { + return LorePredicate(StringMatcher.parse(jsonElement)) + } + } + + override fun test(stack: ItemStack): Boolean { + val display = stack.getOrCreateSubNbt(ItemStack.DISPLAY_KEY) + if (!display.contains(ItemStack.LORE_KEY, NbtElement.LIST_TYPE.toInt())) + return false + val lore = display.getList(ItemStack.LORE_KEY, NbtElement.STRING_TYPE.toInt()) + return lore.any { matcher.matches(it as NbtString)} + } +} diff --git a/src/main/kotlin/moe/nea/firmament/features/texturepack/ModelOverrideData.kt b/src/main/kotlin/moe/nea/firmament/features/texturepack/ModelOverrideData.kt new file mode 100644 index 0000000..ff68c94 --- /dev/null +++ b/src/main/kotlin/moe/nea/firmament/features/texturepack/ModelOverrideData.kt @@ -0,0 +1,12 @@ +/* + * SPDX-FileCopyrightText: 2024 Linnea Gräf + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +package moe.nea.firmament.features.texturepack + +interface ModelOverrideData { + fun getFirmamentOverrides(): Array? + fun setFirmamentOverrides(overrides: Array?) +} diff --git a/src/main/kotlin/moe/nea/firmament/features/texturepack/ModelOverrideFilterSet.kt b/src/main/kotlin/moe/nea/firmament/features/texturepack/ModelOverrideFilterSet.kt new file mode 100644 index 0000000..c8cacbd --- /dev/null +++ b/src/main/kotlin/moe/nea/firmament/features/texturepack/ModelOverrideFilterSet.kt @@ -0,0 +1,24 @@ +/* + * SPDX-FileCopyrightText: 2024 Linnea Gräf + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +package moe.nea.firmament.features.texturepack + +import com.google.gson.JsonElement +import moe.nea.firmament.util.filter.IteratorFilterSet + +class ModelOverrideFilterSet(original: java.util.Set>) : + IteratorFilterSet>(original) { + companion object { + @JvmStatic + fun createFilterSet(set: java.util.Set<*>): java.util.Set<*> { + return ModelOverrideFilterSet(set as java.util.Set>) as java.util.Set<*> + } + } + + override fun shouldKeepElement(element: Map.Entry): Boolean { + return !element.key.startsWith("firmament:") + } +} diff --git a/src/main/kotlin/moe/nea/firmament/features/texturepack/OrPredicate.kt b/src/main/kotlin/moe/nea/firmament/features/texturepack/OrPredicate.kt new file mode 100644 index 0000000..c171367 --- /dev/null +++ b/src/main/kotlin/moe/nea/firmament/features/texturepack/OrPredicate.kt @@ -0,0 +1,31 @@ +/* + * SPDX-FileCopyrightText: 2024 Linnea Gräf + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +package moe.nea.firmament.features.texturepack + +import com.google.gson.JsonArray +import com.google.gson.JsonElement +import com.google.gson.JsonObject +import net.minecraft.item.ItemStack + +class OrPredicate(val children: Array) : FirmamentModelPredicate { + override fun test(stack: ItemStack): Boolean { + return children.any { it.test(stack) } + } + + object Parser : FirmamentModelPredicateParser { + override fun parse(jsonElement: JsonElement): FirmamentModelPredicate { + val children = + (jsonElement as JsonArray) + .flatMap { + CustomModelOverrideParser.parsePredicates(it as JsonObject) + } + .toTypedArray() + return AndPredicate(children) + } + + } +} diff --git a/src/main/kotlin/moe/nea/firmament/features/texturepack/StringMatcher.kt b/src/main/kotlin/moe/nea/firmament/features/texturepack/StringMatcher.kt new file mode 100644 index 0000000..0fb8e00 --- /dev/null +++ b/src/main/kotlin/moe/nea/firmament/features/texturepack/StringMatcher.kt @@ -0,0 +1,70 @@ +/* + * SPDX-FileCopyrightText: 2024 Linnea Gräf + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +package moe.nea.firmament.features.texturepack + +import com.google.gson.JsonElement +import com.google.gson.JsonObject +import com.google.gson.JsonPrimitive +import java.util.function.Predicate +import net.minecraft.nbt.NbtString +import net.minecraft.text.Text +import moe.nea.firmament.util.removeColorCodes + +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) ?: 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) + } + } + + class Pattern(patternWithColorCodes: String, val stripColorCodes: Boolean) : StringMatcher { + private val regex: Predicate = patternWithColorCodes.toPattern().asMatchPredicate() + override fun matches(string: String): Boolean { + return regex.test(if (stripColorCodes) string.removeColorCodes() else string) + } + } + + companion object { + 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["text"] 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/main/kotlin/moe/nea/firmament/util/filter/IteratorFilterSet.kt b/src/main/kotlin/moe/nea/firmament/util/filter/IteratorFilterSet.kt new file mode 100644 index 0000000..61d6524 --- /dev/null +++ b/src/main/kotlin/moe/nea/firmament/util/filter/IteratorFilterSet.kt @@ -0,0 +1,38 @@ +/* + * SPDX-FileCopyrightText: 2024 Linnea Gräf + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +package moe.nea.firmament.util.filter + +abstract class IteratorFilterSet(val original: java.util.Set) : java.util.Set by original { + abstract fun shouldKeepElement(element: K): Boolean + + override fun iterator(): MutableIterator { + val parentIterator = original.iterator() + return object : MutableIterator { + var lastEntry: K? = null + override fun hasNext(): Boolean { + while (lastEntry == null) { + if (!parentIterator.hasNext()) + break + val element = parentIterator.next() + if (!shouldKeepElement(element)) continue + lastEntry = element + } + return lastEntry != null + } + + override fun next(): K { + if (!hasNext()) throw NoSuchElementException() + return lastEntry ?: throw NoSuchElementException() + } + + override fun remove() { + TODO("Not yet implemented") + } + } + } +} + diff --git a/src/main/kotlin/moe/nea/firmament/util/textutil.kt b/src/main/kotlin/moe/nea/firmament/util/textutil.kt index f811bd8..1d61332 100644 --- a/src/main/kotlin/moe/nea/firmament/util/textutil.kt +++ b/src/main/kotlin/moe/nea/firmament/util/textutil.kt @@ -1,5 +1,6 @@ /* * SPDX-FileCopyrightText: 2023 Linnea Gräf + * SPDX-FileCopyrightText: 2024 Linnea Gräf * * SPDX-License-Identifier: GPL-3.0-or-later */ @@ -69,9 +70,30 @@ class TextMatcher(text: Text) { } } +val formattingChars = "kmolnrKMOLNR".toSet() +fun CharSequence.removeColorCodes(keepNonColorCodes: Boolean = false): String { + var nextParagraph = indexOf('§') + if (nextParagraph < 0) return this.toString() + val stringBuffer = StringBuilder(this.length) + var readIndex = 0 + while (nextParagraph >= 0) { + stringBuffer.append(this, readIndex, nextParagraph) + if (keepNonColorCodes && nextParagraph + 1 < length && this[nextParagraph + 1] in formattingChars) { + readIndex = nextParagraph + nextParagraph = indexOf('§', startIndex = readIndex + 1) + } else { + readIndex = nextParagraph + 2 + nextParagraph = indexOf('§', startIndex = readIndex) + } + if (readIndex > this.length) + readIndex = this.length + } + stringBuffer.append(this, readIndex, this.length) + return stringBuffer.toString() +} -val Text.unformattedString - get() = string.replace("§.".toRegex(), "") +val Text.unformattedString: String + get() = string.removeColorCodes().toString() fun Text.transformEachRecursively(function: (Text) -> Text): Text { -- cgit